1 狀態(tài)機(jī)
狀態(tài)轉(zhuǎn)移圖
@startuml
[*] -> active: 新建
active -down-> inactive: 可達(dá)性改變且無注冊隊列
active -right-> pending: 可達(dá)性改變且有注冊隊列
pending -down-> enqueued: 成功加入隊列
enqueued -left-> inactive: 從隊列移除
inactive -left-> [*]
@enduml
說明:
-
active
岔绸。此狀態(tài)需要收集器進(jìn)行特殊處理。當(dāng)收集器檢測到referent的可達(dá)性發(fā)生改變橡伞,它會將實例的狀態(tài)改為為pending
或inactive
盒揉,這取決于實例在創(chuàng)建時是否已注冊到某個隊列。有注冊隊列情況兑徘,實例會添加到pending-Reference列表中刚盈,等待入隊。新建引用實例是活動狀態(tài)挂脑。 -
pending
藕漱。表示pending-Reference列表的元素,等待被Reference-handler處理并入隊崭闲。未注冊引用實例不會有此狀態(tài)肋联。。 -
enqueued
刁俭。表示在注冊隊列中的元素橄仍。當(dāng)實例從ReferenceQueue
中移除時,它將變?yōu)?code>inactive牍戚。未注冊引用實例不會有此狀態(tài)侮繁。盡管Reference
提供了入隊方法,但是收集器執(zhí)行的入隊操作是直接執(zhí)行的翘魄,而不是調(diào)用Reference
的入隊方法鼎天。 -
inactive
。終止態(tài)暑竟,不會在改變斋射。
狀態(tài)編碼
引用的狀態(tài)是通過不同字段值共同體現(xiàn)的育勺。
狀態(tài) | 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 |
如何判斷引用狀態(tài)是否是active
?
基于以上編碼關(guān)系,垃圾收集器只需要對next
字段進(jìn)行判斷罗岖,決定是不是需要進(jìn)行特殊處理涧至。規(guī)則如下:如果next
字段為NULL
,那么實例是active
的桑包;否則南蓬,收集器將會按照普通情況處理。
discovered
字段的用途哑了?
當(dāng)引用狀態(tài)為active
時赘方,為了保證并發(fā)收集器能夠發(fā)現(xiàn)下一個active
引用,同時不影響應(yīng)用線程對這些active
引用執(zhí)行enqueue
操作弱左,收集器使用了discovered
字段記錄了下一個active
引用窄陡。
當(dāng)引用狀態(tài)為pending
時,discovered
字段還記錄了pending列表中的下一個引用拆火。
引用隊列的用途跳夭?
如果程序需要感知對象可達(dá)性的變化時,那么要在創(chuàng)建引用對象時傳入注冊隊列们镜。當(dāng)垃圾收集器發(fā)現(xiàn)referent
可達(dá)性發(fā)生變化時币叹,會將referent
的引用加入到注冊隊列中。此時模狭,引用處于enqueued
狀態(tài)颈抚。程序可以通過阻塞輪詢的方式,從隊列中移除引用胞皱。[2]
已注冊引用和引用注冊隊列的關(guān)系是單向的邪意。也就是說,引用注冊隊列不會跟蹤已注冊引用的狀態(tài)反砌。如果已注冊引用的狀態(tài)變?yōu)椴豢蛇_(dá)雾鬼,那么它永遠(yuǎn)不會進(jìn)入引用注冊隊列。所以宴树,程序需要保證referent
對象的可達(dá)性策菜。
如果保證referent
的可達(dá)性呢?
一種方式酒贬,使用單獨的線程輪詢又憨,并從隊列中刪除引用對象,然后對其進(jìn)行處理锭吨。
另一種方式蠢莺,在執(zhí)行操作引用時進(jìn)行檢查(lazy)。
例如零如,使用弱引用實現(xiàn)弱鍵的哈希表躏将,可以在每次訪問時輪詢其引用隊列锄弱。這就是WeakHashMap
類的工作原理。由于ReferenceQueue.poll
方法只檢查內(nèi)部數(shù)據(jù)結(jié)構(gòu)祸憋,因此会宪,將為哈希表訪問方法增加很少的開銷。
引用是何時入隊的?
收集器在將軟引用和弱引用加入注冊隊列(如果有的話)之前蚯窥,自動清除軟引用和弱引用掸鹅。因此,軟引用和弱引用不需要注冊到隊列中才能發(fā)揮作用拦赠,而虛引用則需要注冊隊列巍沙。虛引用對象會保持可達(dá),除非虛引用被清除或者虛引用對象本身不可達(dá)矛紫。
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
列表中的引用對象。
參考如下代碼颊咬,可見處理過程中,體現(xiàn)了這樣一點牡辽。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 引用類型
類型 | 強(qiáng)引用 | SoftReference |
WeakReference |
PhantomReference |
---|---|---|---|---|
定義 | 如果線程在不遍歷任何引用對象的情況下訪問某個對象麸澜,則該對象是強(qiáng)可達(dá)的。 新建的對象是線程強(qiáng)可達(dá)的奏黑。 |
如果對象不是強(qiáng)可達(dá)的炊邦,但可以通過遍歷軟引用來訪問,則該對象是軟可達(dá)的熟史。 | 如果對象不是強(qiáng)馁害、軟可達(dá)的,但可以通過遍歷弱引用訪問蹂匹,那么它就是弱可達(dá)的碘菜。 當(dāng)對弱可達(dá)對象的弱引用被清除時,就要對該對象執(zhí)行finalization過程限寞。 |
如果一個對象不是強(qiáng)忍啸、軟、弱可達(dá)的履植,那么它就是幻象可達(dá)的计雌,對象已經(jīng)被終止(finalized),但是幻象引用指向了它玫霎。 |
用途 | 普通引用 | 內(nèi)存敏感的緩存 | map | 回收預(yù)清理(代替finalization機(jī)制) |
垃圾回收 | 不可達(dá)時 | 收集器根據(jù)內(nèi)存情況進(jìn)行回收凿滤,保證在OOM之前回收 |
referent 對象狀態(tài)會正常經(jīng)歷finalizable 传泊、finalized ,進(jìn)而被回收 |
PhantomReference 在收集器確定其referent 不需要回收(maybe otherwise be reclaimed)時入隊 |
強(qiáng)度 | 依次減弱 | |||
是否注冊隊列 | - | 否(一般) | 否(一般) | 是 |
當(dāng)對象不屬于上述四種可達(dá)方式時鸭巴,成為不可達(dá)對象眷细。不可達(dá)對象,需要進(jìn)行回收鹃祖。
軟引用代碼示例:
public static void main(String[] args) {
Reference reference = new SoftReference(new Object());
System.gc(); // 輸出結(jié)果非null
System.out.println("Object is: " + reference.get());
}
弱引用代碼示例:
public static void main(String[] args) {
Reference reference = new WeakReference(new Object());
System.gc(); // 注釋掉時溪椎,輸出結(jié)果非null
System.out.println("Object is: " + reference.get());
}
幻象引用代碼示例:
public static void main(String[] args) {
Reference reference = new PhantomReference(new Object(), null);
System.gc(); // 無論是否注釋掉,結(jié)果始終為null恬口。因為重寫了get方法
System.out.println("Object is: " + reference.get());
}