JVM 垃圾回收的核心判斷標準是可達性分析:從 GC Roots 出發,沿著引用鏈搜索,不可達對象判定為可回收。但現實場景中,我們需要更精細的控制:
JDK 1.2 引入四種引用類型,賦予開發者與 GC 協作的能力。
| 引用類型 | 強度 | 回收時機 | 典型場景 | API |
|---|---|---|---|---|
| 強引用 | 最強 | 永不主動回收 (OOM 才拋異常) | 普通對象持有 | Object obj = new Object() |
| 軟引用 | 次強 | 內存不足時 (OOM 前) 回收 | 緩存、對象池 | SoftReference<T> |
| 弱引用 | 較弱 | 下次 GC 必回收 (無論內存) | 監聽器、ThreadLocal、WeakHashMap | WeakReference<T> |
| 虛引用 | 最弱 | 對象最終銷毀前 (配合 ReferenceQueue) | 直接內存釋放、最終化替代 | PhantomReference<T> |
String str = "Hello"; // str 是強引用
// 只有 str 超出作用域、賦值 null、或所在線程結束,對象才可能被回收
誤區澄清:強引用不等於「永不回收」。若強引用鏈從 GC Roots 斷開 (如方法棧幀彈出、集合 remove),對象照樣可回收。循環引用若無外部強引用連接,同樣可回收。
// 創建軟引用
SoftReference softRef = new SoftReference<>(new byte[1024 * 1024]);
// 獲取對象 (可能為 null)
byte[] data = softRef.get();
if (data == null) {
// 重新加載/計算
data = loadData();
softRef = new SoftReference<>(data); // 更新引用
}
get() 更新 timestamp 字段 (記錄最後訪問時間)timestamp 最舊 (最久未用) 的軟引用clock 由 GC 更新,代表當前 GC 週期回收策略:非「全部回收」,而是「按需回收最久未用」,盡量保留熱數據。
WeakReference weakRef = new WeakReference<>(new CacheEntry(key, value));
// 典型應用:ThreadLocalMap 的 Entry 繼承 WeakReference>
// 當 ThreadLocal 實例無強引用時,Entry 自動變弱可達,GC 回收 Key,防止內存洩漏
ReferenceQueue 可感知回收事件// 驗證:弱引用對象在方法執行期間不會被回收
WeakReference
ReferenceQueue
| 維度 | finalize() | PhantomReference + ReferenceQueue |
|---|---|---|
| 可靠性 | 不可靠 (可能不執行、拋異常阻塞、延遲不可控) | 可靠 (GC 保證入隊、可控線程處理) |
| 性能 | 差 (對象進入 Finalizer 佇列、專用線程串行執行) | 好 (直接入 ReferenceQueue、業務線程異步處理) |
| 訪問對象 | 可訪問 (this 強引用) | 不可訪問 (get()=null) |
| 推薦度 | Effective Java 強烈不推薦 | 推薦用於資源清理 |
// ❌ 危險:判空與使用非原子
if (softRef.get() != null) {
softRef.get().doSomething(); // 這行執行前可能被 GC 回收 → NPE
}
// ✅ 正確:獲取強引用再判斷
Object obj = softRef.get();
if (obj != null) {
obj.doSomething(); // 局部變量強引用保護期間安全
}
// WeakHashMap: Key 為弱引用,Value 為強引用
// 當 Key 無外部強引用時,Entry 會被 GC 回收
// ⚠ 注意:Value 可能因被 Entry 強引用而無法回收 (若 Value 間接引用 Key 形成循環)
// 最佳實踐:Value 也用弱引用,或確保無循環引用
SoftReference + ReferenceQueue 定期清理失效引用WeakReference 防止內存洩漏 (如 EventBus 訂閱者)PhantomReference + 專用清理線程ReferenceManager 統一註冊、輪詢 Queue、執行清理四種引用類型構成了 Java 內存管理的精細控制光譜:
不必糾結「優先級」,關鍵在於業務場景需要什麼樣的生命週期承諾。理解引用與 GC 的協作機制,才能寫出既高效又穩健的 Java 代碼。