Reference類簡介

1 狀態機

狀態轉移圖

@startuml
[*] -> active: 新建
active -down-> inactive: 可達性改變且無注冊隊列
active -right-> pending: 可達性改變且有注冊隊列
pending -down-> enqueued: 成功加入隊列
enqueued -left-> inactive: 從隊列移除
inactive -left-> [*]
@enduml
狀態轉移圖

說明:

  1. active。此狀態需要收集器進行特殊處理。當收集器檢測到referent的可達性發生改變,它會將實例的狀態改為為pendinginactive,這取決于實例在創建時是否已注冊到某個隊列。有注冊隊列情況,實例會添加到pending-Reference列表中,等待入隊。新建引用實例是活動狀態。
  2. pending。表示pending-Reference列表的元素,等待被Reference-handler處理并入隊。未注冊引用實例不會有此狀態。。
  3. enqueued。表示在注冊隊列中的元素。當實例從ReferenceQueue中移除時,它將變為inactive。未注冊引用實例不會有此狀態。盡管Reference提供了入隊方法,但是收集器執行的入隊操作是直接執行的,而不是調用Reference的入隊方法。
  4. inactive。終止態,不會在改變。

狀態編碼
引用的狀態是通過不同字段值共同體現的。

狀態 queue next discovered
active 有注冊隊列時ReferenceQueue實例
或者 未注冊隊列時ReferenceQueue.NULL
NULL 下一個discovered列表元素
或者如果是隊尾元素則是this
pending 引用所注冊的ReferenceQueue實例 this 下一個pending列表元素
或者如果是隊尾元素則是NULL
enqueued ReferenceQueue.ENQUEUED 下一個入隊的實例
或者如果是隊尾元素則是this
NULL
inactive ReferenceQueue.NULL this NULL

如何判斷引用狀態是否是active?
基于以上編碼關系,垃圾收集器只需要對next字段進行判斷,決定是不是需要進行特殊處理。規則如下:如果next字段為NULL,那么實例是active的;否則,收集器將會按照普通情況處理。

discovered字段的用途?
當引用狀態為active時,為了保證并發收集器能夠發現下一個active引用,同時不影響應用線程對這些active引用執行enqueue操作,收集器使用了discovered字段記錄了下一個active引用。
當引用狀態為pending時,discovered字段還記錄了pending列表中的下一個引用。

引用隊列的用途?
如果程序需要感知對象可達性的變化時,那么要在創建引用對象時傳入注冊隊列。當垃圾收集器發現referent可達性發生變化時,會將referent的引用加入到注冊隊列中。此時,引用處于enqueued狀態。程序可以通過阻塞輪詢的方式,從隊列中移除引用。[2]

已注冊引用和引用注冊隊列的關系是單向的。也就是說,引用注冊隊列不會跟蹤已注冊引用的狀態。如果已注冊引用的狀態變為不可達,那么它永遠不會進入引用注冊隊列。所以,程序需要保證referent對象的可達性。

如果保證referent的可達性呢?
一種方式,使用單獨的線程輪詢,并從隊列中刪除引用對象,然后對其進行處理。
另一種方式,在執行操作引用時進行檢查(lazy)。
例如,使用弱引用實現弱鍵的哈希表,可以在每次訪問時輪詢其引用隊列。這就是WeakHashMap類的工作原理。由于ReferenceQueue.poll方法只檢查內部數據結構,因此,將為哈希表訪問方法增加很少的開銷。

引用是何時入隊的?
收集器在將軟引用和弱引用加入注冊隊列(如果有的話)之前,自動清除軟引用和弱引用。因此,軟引用和弱引用不需要注冊到隊列中才能發揮作用,而虛引用則需要注冊隊列。虛引用對象會保持可達,除非虛引用被清除或者虛引用對象本身不可達。

public static void main(String[] args) {
    ReferenceQueue referenceQueue = new ReferenceQueue();
    PhantomReference phantomReference = // 如果不聲明本地變量,在gc后將會入隊
            new PhantomReference<>(new Object(), referenceQueue);
    System.gc();
    System.out.println("Object in queue: " + referenceQueue.poll());
}

2 tryHandlePending

處理pending列表中的引用對象。
參考如下代碼,可見處理過程中,體現了這樣一點。discovered字段記錄這pending列表中的下一個元素。

Reference<Object> r;
Cleaner c;
synchronized (lock) {
    if (pending != null) {
        r = pending;
        // 'instanceof' might throw OutOfMemoryError sometimes
        // so do this before un-linking 'r' from the 'pending' chain...
        c = r instanceof Cleaner ? (Cleaner) r : null;
        // unlink 'r' from 'pending' chain
        pending = r.discovered;
        r.discovered = null;
    } else {
        // The waiting on the lock may cause an OutOfMemoryError
        // because it may try to allocate exception objects.
        if (waitForNotify) {
            lock.wait();
        }
        // retry if waited
        return waitForNotify;
    }
}
// Cleaner繼承了PhantomReference,用于一些清理工作
if (c != null) {
    c.clean();
    return true;
}

ReferenceQueue<? super Object> q = r.queue;
// 有注冊隊列,未入隊
if (q != ReferenceQueue.NULL) q.enqueue(r);

3 引用類型

類型 強引用 SoftReference WeakReference PhantomReference
定義 如果線程在不遍歷任何引用對象的情況下訪問某個對象,則該對象是強可達的。
新建的對象是線程強可達的。
如果對象不是強可達的,但可以通過遍歷軟引用來訪問,則該對象是軟可達的。 如果對象不是強、軟可達的,但可以通過遍歷弱引用訪問,那么它就是弱可達的。
當對弱可達對象的弱引用被清除時,就要對該對象執行finalization過程。
如果一個對象不是強、軟、弱可達的,那么它就是幻象可達的,對象已經被終止(finalized),但是幻象引用指向了它。
用途 普通引用 內存敏感的緩存 map 回收預清理(代替finalization機制)
垃圾回收 不可達時 收集器根據內存情況進行回收,保證在OOM之前回收 referent對象狀態會正常經歷finalizable、finalized,進而被回收 PhantomReference在收集器確定其referent不需要回收(maybe otherwise be reclaimed)時入隊
強度 依次減弱
是否注冊隊列 - 否(一般) 否(一般)

當對象不屬于上述四種可達方式時,成為不可達對象。不可達對象,需要進行回收。

軟引用代碼示例:

public static void main(String[] args) {
    Reference reference = new SoftReference(new Object());
    System.gc(); // 輸出結果非null
    System.out.println("Object is: " + reference.get());
}

弱引用代碼示例:

public static void main(String[] args) {
    Reference reference = new WeakReference(new Object());
    System.gc(); // 注釋掉時,輸出結果非null
    System.out.println("Object is: " + reference.get());
}

幻象引用代碼示例:

public static void main(String[] args) {
    Reference reference = new PhantomReference(new Object(), null);
    System.gc(); // 無論是否注釋掉,結果始終為null。因為重寫了get方法
    System.out.println("Object is: " + reference.get());
}

問題

參考資料

  1. Reference
  2. package-summary.html#reachability
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • ReferenceQueue 引用隊列,在檢測到適當的可到達性更改后,垃圾回收器將已注冊的引用對象添加到該隊列中 ...
    tomas家的小撥浪鼓閱讀 36,233評論 10 59
  • 引用類型 JDK1.2之后,Java擴充了引用的概念,將引用分為強引用、軟引用、弱引用和虛引用四種。 強引用類似于...
    德彪閱讀 4,439評論 0 10
  • java.lang.ref 該包下提供了Reference相關的類,包括基類Reference,三個子類WeakR...
    chandarlee閱讀 2,276評論 1 50
  • 沒事瞅了幾眼《中國新聲音》,其中有個女孩性格特別放得開。介紹自己時說導演讓她在人群中,唱歌或者跳舞,她完全OK。年...
    佩小姐的奇妙世界閱讀 445評論 1 2
  • 導引:田老師交流去了,李老師來上課啦!以《歡迎新老師》為題,寫寫你的所見所感所思,80字左右。 田老師: ...
    靈動語文閱讀 2,192評論 0 10