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

Java Reference 機制深度解析:四大引用類型、ReferenceQueue 與 GC 協作流程

     編輯:Java 相關知識

引用類型體系

Java 定義四種引用強度(由強到弱),決定對象被 GC 回收的時機與條件:

引用類型類名回收時機典型用途
強引用普通常量永不主動回收(OOM 前)正常對象持有
軟引用SoftReference內存不足前緩存、圖片池
弱引用WeakReference下次 GC 時WeakHashMap、監聽器
虛引用PhantomReference最終化前,需配合 Queue直接內存釋放、資源清理追蹤
終結引用FinalReferencefinalize() 執行後內部實現 finalize 機制

Reference 核心結構

public abstract class Reference {
    private T referent;                    // 被引用對象
    private volatile ReferenceQueue queue; // 關聯隊列
    @SuppressWarnings("unchecked")
    Reference discovered;               // GC 發現鏈表指針
    // ... 狀態由 (queue, next, discovered) 隱式編碼
}

四種狀態隱式編碼

  1. Active:queue != null && queue != ENQUEUED && discovered == null,對象強可達。
  2. Pending:discovered != null,GC 發現引用對象可回收,掛載在 Reference.pending 靜態鏈表等待處理。
  3. Enqueued:queue == ENQUEUED,已入隊 ReferenceQueue,等待用戶線程輪詢。
  4. Inactive:queue == null,用戶線程調用 clear() 後,referent 已置 null。

ReferenceQueue 實現原理

非標準隊列結構,而是無鎖單向鏈表,head 指向最新入隊節點(LIFO)。

// 入隊
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
lock.notifyAll(); // 喚醒阻塞在 remove() 的線程

// 出隊
Reference r = head;
if (r != null) {
    head = r.next;
    r.next = null;
    r.queue = null; // 狀態轉 Inactive
}
return r;

GC 與 ReferenceHandler 線程協作流程

  1. GC 掃描:發現對象僅被 Reference 引用(非強可達),根據引用類型決定處理策略。
    • SoftReference:依賴 GC 算法(如 CMS 的 SoftRefPolicy)決定是否清除。
    • WeakReference:直接清除 referent,掛載 discovered 鏈。
    • PhantomReference:不清除 referent,直接入隊,保證用戶線程能通過 get() 獲取對象進行清理(如 DirectBuffer 釋放)。
    • FinalReference:調用 finalize(),完成後清除 referent 並入隊。
  2. ReferenceHandler 線程:JVM 啟動時創建,優先級 MAX_PRIORITY,死循環調用 tryHandlePending():
    • 從 pending 鏈取出節點。
    • 若關聯 ReferenceQueue,調用 queue.enqueue(r) 入隊。
    • 否則(無隊列)直接 r.clear() 置空 referent。
  3. 用戶線程:輪詢 queue.remove() 或 poll() 獲取 Reference,執行清理邏輯(關閉流、釋放堆外內存、移除緩存條目等)。

實戰模式:Guava FinalizableReferenceQueue

原生 ReferenceQueue 需自行啟動守護線程輪詢,易漏寫。Guava 封裝:

// 定義帶清理邏輯的引用
class MyFinalizableRef extends FinalizableReference {
    MyFinalizableRef(MyResource referent, FinalizableReferenceQueue queue) {
        super(referent, queue);
    }
    @Override
    public void finalizeReferent() {
        referent.close(); // 釋放資源
    }
}

// 使用
FinalizableReferenceQueue queue = new FinalizableReferenceQueue();
MyResource res = new MyResource();
new MyFinalizableRef(res, queue); // 資源不可達時自動調用 close()

常見誤區與最佳實踐

  • ❌ PhantomReference.get() 永遠返回 null(JDK 9+ 修正為返回 referent,但需 ref.refersTo() 判斷)。
  • ❌ 軟引用不適合精確緩存淘汰(LRU),應配合 LinkedHashMap 或 Caffeine 等專用緩存庫。
  • ✅ WeakHashMap 鍵為弱引用,值為強引用,值對象不可強引用鍵,否則鍵永不回收。
  • ✅ 清理邏輯放在 finalizeReferent() 或隊列消費中,不要重寫 finalize()(已棄用,性能差、不可靠)。
  • ✅ 直接內存 (DirectByteBuffer) 必須用 PhantomReference + Cleaner (JDK 9+) 釋放,避免 OOM。

JDK 9+ 變化:Cleaner API

// 替代 PhantomReference + ReferenceQueue
Cleaner cleaner = Cleaner.create();
Cleaner.Cleanable cleanable = cleaner.register(obj, () -> {
    // 清理邏輯,在 obj 變為 phantom reachable 時執行
    freeNativeMemory(address);
});
// 可手動提前清理
cleanable.clean();

Cleaner 內部使用 PhantomReference,但提供更簡潔 API、線程池隔離、可取消註冊,推薦替代手動 ReferenceQueue 管理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved