Java引用類型
Java引用主要分為4種(其實似乎是5種):
-
Strong Reference 強引用
杈帐,直接引用 -
Soft Reference 軟引用
,間接引用 -
Weak Reference 弱引用
专钉,間接引用 -
Phantom Reference 虛引用
挑童,幾乎無引用 - Final引用,這里不介紹
強引用
Object strongReference = new Object();
我們平常使用最多的就是強引用跃须。按照JVM規(guī)范站叼,在GC時通過可達性分析檢測到強引用可達時,這個對象不會被回收菇民。
但是在某些情況下尽楔,強引用的這個特性會引起OutOfMemoryError
,比如一直向集合中添加元素第练。
軟引用
軟引用對象僅在內(nèi)存不足時會被回收阔馋,JVM保證在拋出OutOfMemoryError
之前已經(jīng)將軟引用對象全部清理了。
弱引用
JVM不保證弱引用對象的回收時機娇掏,在GC線程發(fā)現(xiàn)該對象為弱引用對象時就會回收呕寝。
虛引用
任何時候都可能會被回收。
Java提供了幾種間接引用方式婴梧,有什么好處嗎下梢?是如何起作用的?我們從源碼中一點一點看志秃。
Reference和ReferenceQueue
ReferenceQueue
ReferenceQueue本質(zhì)上是一個單向鏈表怔球,鏈表的節(jié)點是Reference,并提供了同步非阻塞出隊方法poll()
浮还。
同時竟坛,ReferenceQueue提供了一個類似BlockingQueue
的阻塞獲取節(jié)點的方法remove()
。
在Reference類的構(gòu)造方法中,可以指定一個ReferenceQueue
担汤。
Reference
Java的強軟弱虛四種中涎跨,軟、弱崭歧、虛引用都是繼承Reference<T>
抽象類隅很。
核心的幾個屬性和方法:
public abstract class Reference<T> {
......
private T referent; // 代管的對象
......
volatile ReferenceQueue<? super T> queue; // 關(guān)聯(lián)的通知隊列
......
private static Reference<Object> pending = null; // 靜態(tài)的待處理鏈
......
static boolean tryHandlePending(boolean waitForNotify) {......} // 負責檢查pending鏈等關(guān)鍵操作
......
public T get() { return this.referent; } // 獲取直接引用
......
}
Reference類相當于引用代理,正常情況下我們持有Reference對象的引用率碾,Reference對象持有我們真正使用的對象T referent
的引用叔营。
Reference提供了幾個重要的方法:
-
get()
獲取代管對象(referent)的直接引用 -
isEnqueued()
檢查是否還在隊列中 -
tryHandlePending()
如果存在可回收的Reference,則將這個Reference插入到構(gòu)造方法指定的ReferenceQueue中所宰;否則wait()等待
其中绒尊,Reference創(chuàng)建了一個名為“Reference Handler”的常駐后臺線程,用于將referent已被回收的Reference對象加入其引用通知隊列ReferenceQueue仔粥。
Reference中的referent
Reference中的referent會受到GC的特殊對待:
- GC檢測到
reference.referent(非靜態(tài))
引用可達性發(fā)生變化時婴谱,會將對應(yīng)的Reference對象掛在Reference.pending(靜態(tài))
鏈上
ps.文檔未介紹“可達性發(fā)生變化”是什么情況,我猜測是處于“僅Reference對象持有直接引用”的狀態(tài)躯泰。這個狀態(tài)按理說是不能回收的谭羔,因為引用分析是可達的,但這里特殊處理麦向,即使可達GC也可以回收瘟裸。
綜合看Reference和ReferenceQueue
這兩個類形成了一個回收通知機制:
- 新建一個Reference對象,在構(gòu)造方法中為其綁定一個間接引用對象referent磕蛇,及一個回收通知隊列referenceQueue
- 去除referent的其他直接引用
- Reference類在應(yīng)用啟動時就創(chuàng)建了Reference Handler線程景描,但
Reference.tryHandlePending
方法發(fā)現(xiàn)pending對象為null十办,線程進入WAIT
狀態(tài)秀撇,pending保存的是需要回收的Reference鏈的首個節(jié)點 - GC在回收這個Reference對象的referent時,將其Reference對象掛在Reference.pending鏈上向族,即GC會將所有達到可回收狀態(tài)的Reference對象掛到靜態(tài)的pending鏈上
- Reference Handler線程被喚醒呵燕,通過
Reference.tryHandlePending
方法發(fā)現(xiàn)pending中有內(nèi)容,將鏈上的Reference對象插入其構(gòu)造方法指定的referenceQueue中(當然件相,如果沒有指定那么就不會入隊) - 我們通過referenceQueue的
remove()阻塞
或poll()非阻塞
方法可以拿到哪些Reference對象的referent已被回收再扭,可以進行接下來的善后處理
應(yīng)用場景
- 軟引用
官方文檔提到,軟引用非常適合用于內(nèi)存敏感的緩存夜矗,當內(nèi)存不足時GC將會回收緩存中的部分內(nèi)容泛范。
ps.有參考資料提到軟引用可能導(dǎo)致頻繁Full GC。 - 弱引用
官方文檔提到紊撕,弱引用適合用于規(guī)范化映射(Canonicalizing Mappings)罢荡,可以理解為一個Map中的KV映射。 - 虛引用
PhantomReference.get()
方法永遠返回null,因此一旦失去直接引用僅保留虛引用区赵,那么就無法再獲取這個對象的引用惭缰。適合用于監(jiān)控對象是否被回收。
一個典型的應(yīng)用就是WeakHashMap
類笼才。
WeakHashMap中的Entry繼承自WeakReference類漱受,其中key就是間接引用中的referent,獲取key均是通過Reference對象的get()
方法骡送,即有可能失效昂羡。
另外,WeakHashMap持有一個ReferenceQueue對象摔踱,每個Entry都在構(gòu)造方法中關(guān)聯(lián)了這個queue紧憾。
其expungeStaleEntries()
方法會清理失效的Entry,作為大多數(shù)方法的前置操作昌渤。
結(jié)合上邊的回收通知機制等分析赴穗,WeakHashMap有這些特點:
- 使用弱引用作為Entry,referent是Entry的key膀息,并關(guān)聯(lián)了一個內(nèi)部的ReferenceQueue
- 每次GC時都會清理掉一部分Entry的referent般眉,key變?yōu)閚ull——相當于這些內(nèi)容在Map中“消失”了,不過value占用的空間還沒清理
- key被清理的Entry潜支,如前所述會進入回收通知隊列ReferenceQueue
- 在絕大部分操作之前會先從回收通知隊列中獲取失效的Entry甸赃,并從Map中真正刪除
回收通知機制的好處
- GC雖然做了特殊處理,但是插入鏈表這個額外的操作相對來說負擔是非常小的冗酿,對GC影響不大埠对,回收通知機制后續(xù)的邏輯其實是Reference Handler線程在做。
- 使用間接引用后裁替,如何清理被回收的數(shù)據(jù)及相關(guān)數(shù)據(jù)项玛,或者如何接收被回收通知是個問題。
以WeakHashMap舉例弱判,被回收的數(shù)據(jù)都在ReferenceQueue中襟沮,比起遍歷整個集合查看哪些數(shù)據(jù)被回收了,顯然使用通知機制僅查這個queue效率要高得多昌腰。
參考資料
JDK源碼閱讀-Reference
Java Reference詳解 - robin-yao的個人頁面 - OSCHINA
Java 理論與實踐: 用弱引用堵住內(nèi)存泄漏
本文搬自我的博客开伏,歡迎參觀!