Java 引用類型決定了對象的生命週期邊界與 GC 回收策略。除了日常使用的強引用,`java.lang.ref` 包提供了軟引用、弱引用、虛引用三種特殊引用,分別對應「內存敏感緩存」、「規范映射/監聽器」、「對象銷毀追蹤/資源清理」三大核心場景。本文通過源碼級示例、JVM 參數驗證、運行輸出分析,徹底講透四大引用的區別、適用場景與避坑指南。
| 引用類型 | 強度 | GC 回收時機 | 典型用途 | get() 返回 |
|---|---|---|---|---|
| 強引用 | 最強 | 永不主動回收 (OOM 也不回收) | 常規對象持有 | 對象實例 |
| 軟引用 | 次強 | 內存不足 (接近 -Xmx) 時回收 | 可有可無的緩存 (圖片、配置) | 對象實例 / null (已回收) |
| 弱引用 | 較弱 | 下次 GC 發現即回收 (無論內存) | WeakHashMap、監聽器、ThreadLocal 清理 | 對象實例 / null (已回收) |
| 虛引用 | 最弱 | 隨時可能回收,必須配合 ReferenceQueue | 直接內存釋放、對象銷毀追蹤、清理 native 資源 | 永遠返回 null |
public class SoftRefDemo {
private static final ReferenceQueue queue = new ReferenceQueue<>();
static class MyObject {
@Override protected void finalize() { System.out.println("MyObject finalize"); }
@Override public String toString() { return "I am MyObject"; }
}
static class QueueMonitor implements Runnable {
public void run() {
try {
Reference extends MyObject> ref = queue.remove(); // 阻塞等待
System.out.println("Queue received: " + ref.get()); // 已回收,返回 null
} catch (InterruptedException ignored) {}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new QueueMonitor()).start();
MyObject obj = new MyObject(); // 強引用
SoftReference softRef = new SoftReference<>(obj, queue); // 軟引用 + 註冊隊列
obj = null; // 切斷強引用
System.gc(); // 內存充足,不回收
System.out.println("After 1st GC: " + softRef.get()); // I am MyObject
// 分配大數組迫使內存緊張 → 觸發 Full GC
byte[] big = new byte[5 * 1024 * 963]; // ~4.6MB
System.out.println("After allocating big array: " + softRef.get()); // null
System.gc(); // 確保 finalize 執行
Thread.sleep(1000);
}
}
After 1st GC: I am MyObject
After allocating big array: null
MyObject finalize
Queue received: null
public class SoftCache {
private final Map> map = new HashMap<>();
public void put(K k, V v) { map.put(k, new SoftReference<>(v)); }
public V get(K k) {
SoftReference ref = map.get(k);
return ref != null ? ref.get() : null; // 可能返回 null,需重新加載
}
}
WeakReference weakRef = new WeakReference<>(new MyObject(), queue);
System.out.println("Before GC: " + weakRef.get()); // I am MyObject
System.gc();
System.out.println("After GC: " + weakRef.get()); // null
// 隨後 queue.remove() 可獲取被回收的弱引用
PhantomReference phantom = new PhantomReference<>(new MyObject(), queue);
System.out.println("Immediate get: " + phantom.get()); // null (即使對象存活)
new Thread(() -> {
try {
Reference> ref = queue.remove(); // 阻塞直到入隊
System.out.println("Queue got: " + ref.get()); // null
System.exit(0);
} catch (InterruptedException ignored) {}
}).start();
// 刪除強引用
// 第一次 GC:發現對象可回收,執行 finalize(),但虛引用尚未入隊
System.gc(); // 輸出: "MyObject finalize"
// 第二次 GC:對象真正回收,虛引用入隊
System.gc(); // 隊列線程喚醒,打印 "Queue got: null" 並退出
`finalize()` 缺陷:
虛引用方案:
// 清理線程
while (true) {
PhantomReference ref = (PhantomReference) queue.remove();
// ref.get() 為 null,但我們在創建時已綁定外部資源標識
ResourceHandle handle = ref.handle; // 自定義字段存 native 指針/文件句柄
Native.free(handle); // 安全釋放,對象已死不可復活
}
所有三種特殊引用 (軟/弱/虛) 構造時可選註冊隊列,被回收後自動入隊,由專門清理線程處理後續工作 (釋放 native 內存、移除 Map Entry、取消註冊等)。
`ThreadLocalMap` 的 `Entry` 繼承 `WeakReference
需要緩存,內存緊張可丟棄? → SoftReference
需要關聯映射,Key 失效自動清理? → WeakReference / WeakHashMap
需要對象銷毀時釋放 Native 資源/直接內存? → PhantomReference + ReferenceQueue
常規業務對象? → 強引用 (默認)
四大引用類型構建了 Java 內存管理的「分級防護體系」:強引用守住核心對象,軟引用緩衝內存壓力,弱引用維護非侵入式關聯,虛引用守護資源安全釋放。理解它們與 GC 的交互時機、ReferenceQueue 的協作模式,是編寫高性能、無洩漏 Java 程序的進階必修課。