Java 四種引用類型(強、軟、弱、虛)構成了 JVM 記憶體管理的精細分層。面試中常被問及,實戰中 WeakHashMap、緩存框架、ThreadLocal 等廣泛應用。本文系統梳理四種引用特性、引用佇列機制、GC 交互行為與最佳實踐場景。
| 引用類型 | 獲取方式 | GC 回收時機 | 典型用途 | get() 可能返回 null |
|---|---|---|---|---|
| 強引用 | Object obj = new Object() | 永不回收 (OOM 才拋棄) | 普通物件持有 | 否 |
| 軟引用 | SoftReference<T> ref = new SoftReference<>(obj) | 記憶體不足時回收 | 記憶體敏感緩存 (圖片、資料) | 是 |
| 弱引用 | WeakReference<T> ref = new WeakReference<>(obj) | 下次 GC 即回收 | 非必要映射、監聽器、WeakHashMap | 是 |
| 虛引用 | PhantomReference<T> ref = new PhantomReference<>(obj, queue) | 物件最終化後、記憶體釋放前 | 精確追蹤物件銷毀、DirectBuffer 釋放 | 永遠返回 null |
// 問題 1: 緩存導致記憶體洩漏
Map<Widget, String> cache = new HashMap<>();
cache.put(widget, "data"); // 強引用持有 widget,GC 永不回收
// 問題 2: 附加屬性無法擴充第三方類別
class Widget { /* final 或無法修改 */ }
Map<Widget, Integer> serialMap = new HashMap<>();
serialMap.put(widget, 123); // widget 不被回收,序列號常駐記憶體
強引用阻礙 GC 判斷「物件是否仍被需要」,導致手動清理負擔、記憶體洩漏風險。
WeakHashMap 完美解決附加屬性問題// WeakHashMap: Key 為弱引用,Value 為強引用 WeakHashMap<Widget, Integer> serialMap = new WeakHashMap<>(); serialMap.put(widget, 123); // 當 widget 僅被 WeakHashMap 的 Key 引用時 (無其他強引用) // 下次 GC 會回收 widget,WeakHashMap 自動移除對應 Entry // 無需手動清理,無記憶體洩漏風險
WeakHashMap 內部 Key 包裝在 WeakReference 中,並註冊到 ReferenceQueue。ReferenceQueue。WeakHashMap 定期 (如 get/put/size 時) 輪詢佇列,清理對應 Entry。ReferenceQueue<Widget> queue = new ReferenceQueue<>();
WeakReference<Widget> ref = new WeakReference<>(widget, queue);
// 獨立執行緒處理清理
new Thread(() -> {
while (true) {
try {
Reference<? extends Widget> r = queue.remove(); // 阻塞等待
// 執行清理邏輯: 從快取移除、關閉資源、記錄日誌...
doCleanup(r);
} catch (InterruptedException e) { break; }
}
}).start();
優勢:將清理邏輯從業務執行緒剝離,避免 GC 停頓擴大,實現非同步資源釋放。
// 圖片緩存範例
Map<String, SoftReference<Bitmap>> imageCache = new HashMap<>();
public Bitmap getImage(String url) {
SoftReference<Bitmap> ref = imageCache.get(url);
Bitmap bitmap = (ref != null) ? ref.get() : null;
if (bitmap == null) {
bitmap = downloadImage(url);
imageCache.put(url, new SoftReference<>(bitmap));
}
return bitmap;
}
// 記憶體緊張時,JVM 自動回收 SoftReference 持有的 Bitmap
// 下次訪問 ref.get() 返回 null,觸發重新下載
比弱引用「更堅持」地持有物件,適合「寧可重新計算/下載,也不可 OOM」的緩存場景。
get() 永遠返回 null,無法復活物件。ReferenceQueue。finalize() 更可靠、高效:finalize() 需兩次 GC 週期、可能復活物件、執行不確定;虛引用單次 GC 即入隊、不可復活、可精確觸發離堆記憶體釋放 (如 DirectByteBuffer)。// 載入超大圖片序列
PhantomReference<byte[]> lastImageRef = null;
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for (String path : hugeImagePaths) {
// 等待上一張圖片真正被釋放
if (lastImageRef != null) {
try { queue.remove(5000); } catch (InterruptedException ignored) {}
}
byte[] imageData = loadImage(path); // 佔用大量堆外/堆內記憶體
lastImageRef = new PhantomReference<>(imageData, queue);
process(imageData);
}
// 確保同一時間記憶體中僅有一張大圖
finalize() 為何被棄用 (Java 9 Deprecated, Java 18 移除)try-with-resources (資源管理)、Cleaner / PhantomReference (物件銷毀回調)。
需要緩存、希望記憶體緊張時自動釋放?
├─ 是 → SoftReference (圖片、計算結果緩存)
└─ 否 → 需要 Key-Value 映射、Key 無其他引用時自動移除?
├─ 是 → WeakHashMap / WeakReference (監聽器、元資料附加)
└─ 否 → 需要精確知道物件何時被釋放、釋放離堆記憶體?
├─ 是 → PhantomReference + ReferenceQueue (DirectBuffer、大物件流控)
└─ 否 → 普通強引用即可
Reference 類別封裝 referent (被引用物件)、queue、next (佇列鏈表)。Reference 物件,根據引用類型決定處理:ReferenceHandler 守護執行緒持續輪詢佇列,喚醒 Reference.cleaner 或用戶註冊的清理邏輯。深入理解四種引用與 GC 的協作機制,能讓你在記憶體敏感系統、高效能緩存、離堆資源管理等場景做出正確架構決策。