面試資深 Java 工程師時,若問「弱引用怎麼用?」十之八九答不上來。其實引用類型是 JVM 記憶體管理的核心抓手,掌握它們能優雅解決快取、監聽器洩漏、大物件釋放等棘手問題。本文系統梳理四大引用類型、ReferenceQueue 機制與實戰場景。
| 引用類型 | 建立方式 | GC 回收時機 | 典型用途 |
|---|---|---|---|
| 強引用 | 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) | 物件最終化後、記憶體釋放前 | 精確感知物件銷毀、大物件換載同步、替代 finalize |
建立引用時若傳入 ReferenceQueue,當 referent 被 GC 判定為可回收,該引用物件會自動入隊。應用端可啟動守護執行緒輪詢隊列執行清理邏輯:
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Widget> ref = new WeakReference<>(widget, queue);
// 清理執行緒
while (true) {
Reference<?> r = queue.remove(); // 阻塞等待
cleanup(r); // 移除 WeakHashMap entry、釋放原生資源等
}
鍵採弱引用,當鍵無強引用指向時,entry 自動移除,極適合「元資料附掛」場景:
Map<Widget, SerialNumber> map = new WeakHashMap<>();
map.put(widget, sn); // widget 其它地方釋放後,此 entry 自動消失
public class ImageCache {
private final Map<String, SoftReference<BufferedImage>> cache = new HashMap<>();
public BufferedImage get(String key) {
SoftReference<BufferedImage> ref = cache.get(key);
return ref != null ? ref.get() : null;
}
public void put(String key, BufferedImage img) {
cache.put(key, new SoftReference<>(img));
}
}
// JVM 記憶體緊張時自動清理 SoftReference,避免 OOM
get() 永遠回傳 null,防止物件復活| 場景 | 推薦引用 | 理由 |
|---|---|---|
| 圖片/網頁快取 | SoftReference | 記憶體充裕時保留,不足時釋放 |
| 監聽器/觀察者註冊 | WeakReference | 避免監聽器阻礙被觀察者回收 |
| ClassLoader 快取 | WeakReference | 類卸載時自動清理 |
| DirectByteBuffer 釋放 | PhantomReference + Cleaner | 精確感知記憶體釋放時機 |
| ThreadLocal 洩漏防護 | WeakReference (Key) | ThreadLocalMap 內部即採弱引用鍵 |
WeakHashMap 的 value 仍為強引用,若 value 反向持有 key 會阻礙回收PhantomReference 替代 try-with-resources 或 Cleaner (Java 9+)引用類型是 JVM 給開發者的「記憶體管理遙控器」。理解強→軟→弱→虛的語意差異,配合 ReferenceQueue 非同步清理,能讓快取、資源管理、洩漏防護的代碼既優雅又健壯。下次面試遇到這題,請自信地畫出上表即可。