首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 弱引用深度解析:原理、場景與 WeakHashMap 實戰

     編輯:Java 相關知識

四大引用類型體系

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);
        }
    }
}

常見誤區與避坑指南

  1. 誤以為弱引用能「延緩」回收:弱引用不阻止 GC,下次 GC 必回收。需延緩請用軟引用。
  2. WeakHashMap 當作一般 Map 用:鍵必須正確實作 equals()/hashCode(),且不應為長生命週期物件(如 String 常量、單例),否則永不回收。
  3. 忽略佇列清理:大量弱引用堆積會拖慢 GC,必須配合 ReferenceQueue 定期清理。
  4. 多執行緒競態:weakRef.get() 非原子,檢查非 null 到使用之間物件可能被回收。關鍵區段需同步或用 AtomicReference 包裝。

效能考量

  • 弱引用存取比強引用多一次間接層級(get()),高頻熱點路徑需評估影響
  • 大量弱引用物件會增加 GC 掃描與佇列處理開銷
  • JDK 9+ 引入 java.lang.ref.Cleaner 作為更現代的清理機制,建議新專案優先使用
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved