聊聊JVM系列一 對(duì)象死了嗎?

程序員:我辛辛苦苦創(chuàng)建的對(duì)象,GC你卻把他回收了,你還我對(duì)象還我對(duì)象;

GC:好啊,摘下星星給你,摘下月亮給你,摘下太陽(yáng)給你,你要的都給你, 于是 -> java.lang.OutOfMemoryError:PermGen space ......

作為一個(gè)心地善良正直勇敢的程序員,刪過庫(kù),跑過路,我害怕一個(gè)小小的內(nèi)存溢出,老板,服務(wù)器內(nèi)存不夠了,再來100個(gè)G

你知道的,JVM作為程序員的好伙伴,我們一般不會(huì)關(guān)心他默默為我們做了那些事情,他像水一樣無(wú)形的存在,除非下大雨發(fā)了洪水(OutOfMemoryError:PermGen 一類的狀況)我們才發(fā)現(xiàn),哦,原來他這么厲害啊,于是就緊急著翻書,百度,谷歌各種錯(cuò)誤,尋求解決之道,這篇文章就是來聊聊和我們平時(shí)默默打交道JVM如何判斷對(duì)象已死.

先來看張圖:
image

<figcaption style="box-sizing: border-box; outline: 0px; display: block; text-align: center; margin: 8px; color: rgb(153, 153, 153); font-size: 14px; overflow-wrap: break-word;">對(duì)象引用狀態(tài)圖</figcaption>

了解對(duì)象的引用狀態(tài)可以幫助你了解GC回收的時(shí)候是如何回收的,回收那些對(duì)象,怎么判斷對(duì)象死沒死!
  • 強(qiáng)引用

    特點(diǎn):我們平常典型編碼Object obj = new Object()中的obj就是強(qiáng)引用。通過關(guān)鍵字new創(chuàng)建的對(duì)象所關(guān)聯(lián)的引用就是強(qiáng)引用。 當(dāng)JVM內(nèi)存空間不足,JVM寧愿拋出OutOfMemoryError運(yùn)行時(shí)錯(cuò)誤(OOM),使程序異常終止,也不會(huì)靠隨意回收具有強(qiáng)引用的“存活”對(duì)象來解決內(nèi)存不足的問題。對(duì)于一個(gè)普通的對(duì)象,如果沒有其他的引用關(guān)系,只要超過了引用的作用域或者顯式地將相應(yīng)(強(qiáng))引用賦值為 null,就是可以被垃圾收集的了,具體回收時(shí)機(jī)還是要看垃圾收集策略。

  • 軟引用

特點(diǎn):軟引用通過SoftReference類實(shí)現(xiàn)。 軟引用的生命周期比強(qiáng)引用短一些。只有當(dāng) JVM 認(rèn)為內(nèi)存不足時(shí),才會(huì)去試圖回收軟引用指向的對(duì)象:即JVM 會(huì)確保在拋出 OutOfMemoryError 之前,清理軟引用指向的對(duì)象。軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收器回收,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。后續(xù),我們可以調(diào)用ReferenceQueue的poll()方法來檢查是否有它所關(guān)心的對(duì)象被回收。如果隊(duì)列為空,將返回一個(gè)null,否則該方法返回隊(duì)列中前面的一個(gè)Reference對(duì)象。

應(yīng)用場(chǎng)景:軟引用通常用來實(shí)現(xiàn)內(nèi)存敏感的緩存。如果還有空閑內(nèi)存,就可以暫時(shí)保留緩存,當(dāng)內(nèi)存不足時(shí)清理掉,這樣就保證了使用緩存的同時(shí),不會(huì)耗盡內(nèi)存。

  • 弱引用

    特點(diǎn):弱引用通過WeakReference類實(shí)現(xiàn)。 弱引用的生命周期比軟引用短。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快回收弱引用的對(duì)象。弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

    應(yīng)用場(chǎng)景:弱應(yīng)用同樣可用于內(nèi)存敏感的緩存。

  • 虛引用

特點(diǎn):虛引用也叫幻象引用,通過PhantomReference類來實(shí)現(xiàn)。無(wú)法通過虛引用訪問對(duì)象的任何屬性或函數(shù)?;孟笠脙H僅是提供了一種確保對(duì)象被 finalize 以后,做某些事情的機(jī)制。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。虛引用必須和引用隊(duì)列 (ReferenceQueue)聯(lián)合使用。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

ReferenceQueue queue = new ReferenceQueue ();PhantomReference pr = new PhantomReference (object, queue); 

程序可以通過判斷引用隊(duì)列中是否已經(jīng)加入了虛引用,來了解被引用的對(duì)象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取一些程序行動(dòng)。
** 應(yīng)用場(chǎng)景**:可用來跟蹤對(duì)象被垃圾回收器回收的活動(dòng),當(dāng)一個(gè)虛引用關(guān)聯(lián)的對(duì)象被垃圾收集器回收之前會(huì)收到一條系統(tǒng)通知。

簡(jiǎn)單總結(jié)一下:強(qiáng)引用就像大老婆,關(guān)系很穩(wěn)固。
軟引用就像二老婆,隨時(shí)有失寵的可能,但也有扶正的可能。
弱引用就像情人,關(guān)系不穩(wěn)定,可能跟別人跑了。
幻像引用就是夢(mèng)中情人,只在夢(mèng)里出現(xiàn)過。

知道了各個(gè)對(duì)象的引用關(guān)系,那么GC是何時(shí)進(jìn)行回收呢,這里有一個(gè)算法叫做<u style="box-sizing: border-box; outline: 0px; overflow-wrap: break-word;">可達(dá)性分析算法</u>,敲黑板,劃重點(diǎn)了,期末考試要考,該算法的基本思路就是通過一些被稱為引用鏈(GC Roots)的對(duì)象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索走過的路徑被稱為(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí)(即從GC Roots節(jié)點(diǎn)到該節(jié)點(diǎn)不可達(dá)),則證明該對(duì)象是不可用的??磮D:

image
如上圖所示,object1~object4對(duì)GC Root都是可達(dá)的,說明不可被回收,object5~object7對(duì)GC Root節(jié)點(diǎn)不可達(dá),說明其可以被回收。

在Java中,可作為GC Root的對(duì)象包括以下幾種:

  • 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象

  • 方法區(qū)中類靜態(tài)屬性引用的對(duì)象

  • 方法區(qū)中常量引用的對(duì)象

  • 本地方法棧中JNI(即一般說的Native方法)引用的對(duì)象

    總結(jié)一下,哪些不可以回收,static修飾的對(duì)象,棧中地址指向堆中的對(duì)象,方法區(qū)中包含String常量和各個(gè)常量信息,JNI引用的對(duì)象,除了這些的對(duì)象都是可以被回收的對(duì)象,但是可以被回收的對(duì)象并不代表已經(jīng)死亡,有的對(duì)象是有BUFF加持的,可以僥幸逃過GC的追殺令,那么這個(gè)是怎么實(shí)現(xiàn)的!

    那是因?yàn)榧词乖诳蛇_(dá)性分析算法中不可達(dá)的對(duì)象,也并非是“非死不可”的,這時(shí)候它們暫時(shí)處于“緩刑”階段,要真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷再次標(biāo)記過程。
    標(biāo)記的前提是對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈。

  • 1. 第一次標(biāo)記并進(jìn)行一次篩選。
    篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法。
    當(dāng)對(duì)象沒有覆蓋finalize方法,或者finzlize方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”,對(duì)象被回收。

  • 2. 第二次標(biāo)記
    如果這個(gè)對(duì)象被判定為有必要執(zhí)行finalize()方法,那么這個(gè)對(duì)象將會(huì)被放置在一個(gè)名為:F-Queue的隊(duì)列之中,并在稍后由一條虛擬機(jī)自動(dòng)建立的、低優(yōu)先級(jí)的Finalizer線程去執(zhí)行。這里所謂的“執(zhí)行”是指虛擬機(jī)會(huì)觸發(fā)這個(gè)方法,但并不承諾會(huì)等待它運(yùn)行結(jié)束。這樣做的原因是,如果一個(gè)對(duì)象finalize()方法中執(zhí)行緩慢,或者發(fā)生死循環(huán)(更極端的情況),將很可能會(huì)導(dǎo)致F-Queue隊(duì)列中的其他對(duì)象永久處于等待狀態(tài),甚至導(dǎo)致整個(gè)內(nèi)存回收系統(tǒng)崩潰。
    Finalize()方法是對(duì)象脫逃死亡命運(yùn)的最后一次機(jī)會(huì),稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模標(biāo)記,如果對(duì)象要在finalize()中成功拯救自己----只要重新與引用鏈上的任何的一個(gè)對(duì)象建立關(guān)聯(lián)即可,譬如把自己賦值給某個(gè)類變量或?qū)ο蟮某蓡T變量,那在第二次標(biāo)記時(shí)它將移除出“即將回收”的集合。如果對(duì)象這時(shí)候還沒逃脫,那基本上它就真的被回收了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容