Java 依據「可達性分析」定義四種引用強度,由強到弱:
| 引用類型 | 類別 | 回收條件 | 典型場景 |
|---|---|---|---|
| 強引用 | 普遍 new Object() | GC 根可達即不回收 | 一般物件持有 |
| 軟引用 | SoftReference<T> | 記憶體不足時回收 | 記憶體敏感快取(圖片、配置) |
| 弱引用 | WeakReference<T> | 下次 GC 必回收 | 標準化映射、監聽器註冊、WeakHashMap |
| 虛引用 | PhantomReference<T> | 無法單獨獲取物件 | 精確追蹤物件回收時機、DirectBuffer 清理 |
// 基本用法
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
// 獲取被引用物件(可能為 null)
Object retrieved = weakRef.get(); // 若已被 GC 回收,返回 null
建構時指定 ReferenceQueue,GC 回收弱引用指向的物件後,會將弱引用物件本身入隊,供統一清理:
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> weakRef = new WeakReference<>(obj, queue);
// 後台執行緒定期處理
new Thread(() -> {
try {
while (true) {
WeakReference<?> ref = (WeakReference<?>) queue.remove(); // 阻塞等待
// 執行清理邏輯:移除外部映射、釋放資源等
cleanup(ref);
}
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}).start();
問題:使用 HashMap<Key, Value> 做快取,Key 不再使用時,Map 仍強引用 Key,導致無法回收,需手動移除。
解決:WeakHashMap 內部鍵為 WeakReference,當 Key 僅被 WeakHashMap 引用時,GC 會回收 Key,Entry 自動失效。
// 模擬產品編號快取
class Product { String id; /* ... */ }
Map<Product, String> cache = new WeakHashMap<>();
Product p1 = new Product("P1001");
cache.put(p1, "高階筆電");
// 當 p1 置 null,且無其他強引用時
p1 = null;
System.gc(); // 強制 GC(演示用)
// 下次存取時,對應 Entry 已自動移除
System.out.println(cache.size()); // 0
注意:Value 仍為強引用,若 Value 間接持有 Key,會阻礙回收。適用於「Key 生命週期由外部控制、Value 不回引 Key」的場景。
事件總線註冊監聽器,若監聽器未顯式註銷,總線強引用導致無法回收。
public class EventBus {
// 使用 WeakReference 儲存監聽器
private final List<WeakReference<Listener>> listeners = new ArrayList<>();
private final ReferenceQueue<Listener> queue = new ReferenceQueue<>();
public void register(Listener l) {
cleanUp(); // 註冊時順帶清理失效引用
listeners.add(new WeakReference<>(l, queue));
}
public void post(Event e) {
cleanUp();
for (WeakReference<Listener> ref : listeners) {
Listener l = ref.get();
if (l != null) l.onEvent(e);
}
}
private void cleanUp() {
WeakReference<?> ref;
while ((ref = queue.poll()) != null) {
listeners.remove(ref);
}
}
}
equals()/hashCode(),且不應為長生命週期物件(如 String 常量、單例),否則永不回收。ReferenceQueue 定期清理。weakRef.get() 非原子,檢查非 null 到使用之間物件可能被回收。關鍵區段需同步或用 AtomicReference 包裝。get()),高頻熱點路徑需評估影響java.lang.ref.Cleaner 作為更現代的清理機制,建議新專案優先使用