在 Java 自動內存管理模型中,「引用」是對象生存的生命線。JDK 1.2 引入四種引用強度,賦予開發者與 GC 協作的能力:精確控制對象生命周期、實現緩存淘汰、防止 OOM、實現對象終結監聽。
| 引用類型 | 類名 | 回收時機 | 典型用途 | 是否影響對象生命周期 |
|---|---|---|---|---|
| 強引用 | 無 (普遍變量) | 永不回收 (寧願 OOM) | 普通對象持有 | 是,強綁定 |
| 軟引用 | SoftReference | 內存不足時 | 緩存 (圖片、網頁、數據) | 否,可被 GC 犧牲 |
| 弱引用 | WeakReference | 下次 GC 時 | 緩存、監聽器、ThreadLocal | 否,生命周期極短 |
| 虛引用 | PhantomReference | 隨時 (配合 ReferenceQueue) | 堆外內存釋放、對象銷毀監聽 | 否,完全不影響 |
String s = "hello"; // s 是強引用
// 只要 s 在作用域內且可達,"hello" 絕不會被回收
// 甚至 JVM 拋出 OutOfMemoryError 也不會回收強引用對象
import java.lang.ref.SoftReference;
import java.util.HashMap;
public class ImageCache {
// 緩存結構:路徑 -> 軟引用
private final Map<String, SoftReference<Bitmap>> cache = new HashMap<>();
public void put(String path, Bitmap bitmap) {
cache.put(path, new SoftReference<>(bitmap));
}
public Bitmap get(String path) {
SoftReference<Bitmap> ref = cache.get(path);
return ref != null ? ref.get() : null; // 可能返回 null (已被 GC)
}
// 結合 ReferenceQueue 自動清理無效條目 (進階)
}
GC 行為:JVM 保證在拋出 OOM 前,會盡最大努力回收所有軟引用對象。`-XX:SoftRefLRUPolicyMSPerMB=1000` 可調整免費內存中軟引用存活時間 (默認 1s/MB)。
import java.lang.ref.WeakReference;
WeakReference<Object> weakRef = new WeakReference<>(new Object());
System.out.println(weakRef.get()); // 非 null
System.gc(); // 強制 GC
System.out.println(weakRef.get()); // null (幾乎必定被回收)
核心場景:
WeakHashMap:鍵為弱引用,鍵無外部強引用時自動移除條目。ThreadLocal:鍵為弱引用,防止線程池場景下 ThreadLocalMap 洩漏。import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantom = new PhantomReference<>(obj, queue);
obj = null; // 切斷強引用
System.gc(); // 觸發 GC
// 虛引用 get() 永遠返回 null,無法通過它訪問對象
System.out.println(phantom.get()); // null
// 但引用會入隊,可作為「對象即將被回收」的信號
if (queue.poll() != null) {
System.out.println("對象已被 GC 回收,可執行清理動作 (如釋放堆外內存)");
}
關鍵特性:
get() 永遠返回 null,無法復活對象。ReferenceQueue 使用,否則無意義。finalize) 完成後、內存真正釋放前,虛引用入隊。Cleaner (Java 9+) 和 DirectByteBuffer 釋放堆外內存的基石。ReferenceQueue 是軟/弱/虛引用與 GC 通訊的管道:
new SoftReference(obj, queue)。queue.poll() 或阻塞 queue.remove() 獲取已失效引用。JVM 通過可達性分析判斷對象生死,引用強度決定可達等級:
| 誤區 | 後果 | 正確做法 |
|---|---|---|
| 軟引用緩存不配合隊列清理 | Map 滿是失效 null 值,內存洩漏 | 定期輪詢 queue.poll() 移除無效 key |
| 弱引用用於「重要但非必需」緩存 | 緩存極易失效,命中率極低 | 此類場景用軟引用 |
| 虛引用試圖獲取對象 (get()) | 永遠為 null,業務邏輯失效 | 僅作信號,配合隊列做清理 |
| ThreadLocal 不 remove() 依賴弱鍵 | 線程池場景下 Value 洩漏 | 用完必須 remove(),弱鍵只防 Key 洩漏 |
四大引用是 Java 內存管理的精密齒輪: