Java的強洲押、軟、弱圆凰、虛引用介紹與分析

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
這兩個類形成了一個回收通知機制:

  1. 新建一個Reference對象,在構(gòu)造方法中為其綁定一個間接引用對象referent磕蛇,及一個回收通知隊列referenceQueue
  2. 去除referent的其他直接引用
  3. Reference類在應(yīng)用啟動時就創(chuàng)建了Reference Handler線程景描,但Reference.tryHandlePending方法發(fā)現(xiàn)pending對象為null十办,線程進入WAIT狀態(tài)秀撇,pending保存的是需要回收的Reference鏈的首個節(jié)點
  4. GC在回收這個Reference對象的referent時,將其Reference對象掛在Reference.pending鏈上向族,即GC會將所有達到可回收狀態(tài)的Reference對象掛到靜態(tài)的pending鏈上
  5. Reference Handler線程被喚醒呵燕,通過Reference.tryHandlePending方法發(fā)現(xiàn)pending中有內(nèi)容,將鏈上的Reference對象插入其構(gòu)造方法指定的referenceQueue中(當然件相,如果沒有指定那么就不會入隊)
  6. 我們通過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有這些特點:

  1. 使用弱引用作為Entry,referent是Entry的key膀息,并關(guān)聯(lián)了一個內(nèi)部的ReferenceQueue
  2. 每次GC時都會清理掉一部分Entry的referent般眉,key變?yōu)閚ull——相當于這些內(nèi)容在Map中“消失”了,不過value占用的空間還沒清理
  3. key被清理的Entry潜支,如前所述會進入回收通知隊列ReferenceQueue
  4. 在絕大部分操作之前會先從回收通知隊列中獲取失效的Entry甸赃,并從Map中真正刪除

回收通知機制的好處

  1. GC雖然做了特殊處理,但是插入鏈表這個額外的操作相對來說負擔是非常小的冗酿,對GC影響不大埠对,回收通知機制后續(xù)的邏輯其實是Reference Handler線程在做。
  2. 使用間接引用后裁替,如何清理被回收的數(shù)據(jù)及相關(guān)數(shù)據(jù)项玛,或者如何接收被回收通知是個問題。
    以WeakHashMap舉例弱判,被回收的數(shù)據(jù)都在ReferenceQueue中襟沮,比起遍歷整個集合查看哪些數(shù)據(jù)被回收了,顯然使用通知機制僅查這個queue效率要高得多昌腰。

參考資料

JDK源碼閱讀-Reference
Java Reference詳解 - robin-yao的個人頁面 - OSCHINA
Java 理論與實踐: 用弱引用堵住內(nèi)存泄漏

本文搬自我的博客开伏,歡迎參觀!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遭商,一起剝皮案震驚了整個濱河市固灵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌劫流,老刑警劉巖巫玻,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件暑认,死亡現(xiàn)場離奇詭異,居然都是意外死亡大审,警方通過查閱死者的電腦和手機蘸际,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來徒扶,“玉大人粮彤,你說我怎么就攤上這事〗猓” “怎么了导坟?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長圈澈。 經(jīng)常有香客問我惫周,道長,這世上最難降的妖魔是什么康栈? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任递递,我火速辦了婚禮,結(jié)果婚禮上啥么,老公的妹妹穿的比我還像新娘登舞。我一直安慰自己,他們只是感情好悬荣,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布菠秒。 她就那樣靜靜地躺著,像睡著了一般氯迂。 火紅的嫁衣襯著肌膚如雪践叠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天嚼蚀,我揣著相機與錄音禁灼,去河邊找鬼。 笑死驰坊,一個胖子當著我的面吹牛匾二,可吹牛的內(nèi)容都是我干的哮独。 我是一名探鬼主播拳芙,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼皮璧!你這毒婦竟也來了舟扎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤悴务,失蹤者是張志新(化名)和其女友劉穎睹限,沒想到半個月后譬猫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡羡疗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年染服,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叨恨。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡柳刮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痒钝,到底是詐尸還是另有隱情秉颗,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布送矩,位于F島的核電站蚕甥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏栋荸。R本人自食惡果不足惜菇怀,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晌块。 院中可真熱鬧敏释,春花似錦、人聲如沸摸袁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽靠汁。三九已至蜂大,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝶怔,已是汗流浹背奶浦。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留踢星,地道東北人澳叉。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像沐悦,于是被迫代替她去往敵國和親成洗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容