溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java的垃圾回收怎么理解

發(fā)布時(shí)間:2022-04-08 09:07:18 來源:億速云 閱讀:100 作者:iii 欄目:開發(fā)技術(shù)

這篇“Java的垃圾回收怎么理解”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Java的垃圾回收怎么理解”文章吧。

在說記憶集和卡表之前,先給大家介紹一下跨代引用的問題。

Java的垃圾回收怎么理解

假如要現(xiàn)在進(jìn)行一次只局限于新生代區(qū)域內(nèi)的收集(Minor GC),但新生代的實(shí)例對(duì)象1在老年代中被引用,為了找出該區(qū)域(新生代)中所有的存活對(duì)象,不得不在固定的GC Roots之外,再額外遍歷整個(gè)老年代中所有對(duì)象來確??蛇_(dá)性分析結(jié)果的正確性,反過來也是一樣。遍歷整個(gè)老年代所有對(duì)象的方案雖然理論上可行,但無疑會(huì)為內(nèi)存回收帶來很大的性能負(fù)擔(dān)。

事實(shí)上并不只是新生代、老年代之間才有跨代引用的問題,所有涉及部分區(qū)域收集(Partial GC)行為的垃圾收集器,典型的如G1、ZGC和Shenandoah收集器,都會(huì)面臨相同的問題。

那么如何才能解決跨代引用呢?

首先,跨代引用相對(duì)于同代引用來說僅占極少數(shù)。原因是跨代引用的對(duì)象應(yīng)該傾向于同時(shí)生存或者同時(shí)死亡的(舉個(gè):如果某個(gè)新生代對(duì)象存在跨代引用,由于老年代對(duì)象難以消亡,該引用會(huì)使得新生代對(duì)象在收集時(shí)同樣得以存活,進(jìn)而在年齡增長(zhǎng)之后晉升到老年代中,這時(shí)跨代引用也隨即被消除了)。

依據(jù)上面說所,就不應(yīng)再為了少量的跨代引用去掃描整個(gè)老年代,也不必浪費(fèi)空間專門記錄每一個(gè)對(duì)象是否存在及存在哪些跨代引用,只需在新生代上建立一個(gè)全局的數(shù)據(jù)結(jié)構(gòu)(該結(jié)構(gòu)被稱為“記憶集”,Remembered Set),這個(gè)結(jié)構(gòu)把老年代劃分成若干小塊,標(biāo)識(shí)出老年代的哪一塊內(nèi)存會(huì)存在跨代引用。此后當(dāng)發(fā)生Minor GC時(shí),只有包含了跨代引用的小塊內(nèi)存里的對(duì)象才會(huì)被加入到GCRoots進(jìn)行掃描。雖然這種方法需要在對(duì)象改變引用關(guān)系(如將自己或者某個(gè)屬性賦值)時(shí)維護(hù)記錄數(shù)據(jù)的正確性,會(huì)增加一些運(yùn)行時(shí)的開銷,但比起收集時(shí)掃描整個(gè)老年代來說仍然是劃算的。

下面就來介紹一下這個(gè)全局的數(shù)據(jù)結(jié)構(gòu)記憶集。

記憶集

記憶集是一種用于記錄從非收集區(qū)域指向收集區(qū)域的指針集合的抽象數(shù)據(jù)結(jié)構(gòu)。如果我們不考慮效率和成本的話,最簡(jiǎn)單的實(shí)現(xiàn)可以用非收集區(qū)域中所有含跨代引用的對(duì)象數(shù)組來實(shí)現(xiàn)這個(gè)數(shù)據(jù)結(jié)構(gòu),如下面代碼所示:

//以對(duì)象指針來實(shí)現(xiàn)記憶集的偽代碼
Class RememberedSet {
	Object[] set[OBJECT_INTERGENERATIONAL_REFERENCE_SIZE]; 
}

這種記錄全部含跨代引用對(duì)象的實(shí)現(xiàn)方案,無論是空間占用還是維護(hù)成本都相當(dāng)高昂。而在垃圾收集的場(chǎng)景中,收集器只需要通過記憶集判斷出某一塊非收集區(qū)域是否存在有指向了收集區(qū)域的指針就可以了,并不需要了解這些跨代指針的全部細(xì)節(jié)。那設(shè)計(jì)者在實(shí)現(xiàn)記憶集的時(shí)候,便可以選擇更為粗獷的記錄粒度來節(jié)省記憶集的存儲(chǔ)和維護(hù)成本。下面列舉了一些可供選擇(當(dāng)然也可以選擇這個(gè)范圍以外的)的記錄精度:

  • 字長(zhǎng)精度:每個(gè)記錄精確到一個(gè)機(jī)器字長(zhǎng)(就是處理器的尋址位數(shù),如常見的32位或64位,這個(gè) 精度決定了機(jī)器訪問物理內(nèi)存地址的指針長(zhǎng)度),該字包含跨代指針。

  • 對(duì)象精度:每個(gè)記錄精確到一個(gè)對(duì)象,該對(duì)象里有字段含有跨代指針。

  • 卡精度:每個(gè)記錄精確到一塊內(nèi)存區(qū)域,該區(qū)域內(nèi)有對(duì)象含有跨代指針。

上面的,第三種“卡精度”所指的是用一種稱為“卡表”(Card Table)的方式去實(shí)現(xiàn)記憶集,這也是目前最常用的記憶集的實(shí)現(xiàn)形式。

卡表和記憶集又有什么關(guān)系呢?

前面介紹記憶集的時(shí)候提到 記憶集其實(shí)是一種"抽象”的數(shù)據(jù)結(jié)構(gòu),抽象的意思是只定義了記憶集的行為意圖,并沒有定義其行為的具體實(shí)現(xiàn)。卡表就是記憶集的一種具體實(shí)現(xiàn),它定義了記憶集的記錄精度、與堆內(nèi)存的映射關(guān)系等。關(guān)于記憶集與卡表的關(guān)系,可以按照J(rèn)ava中Map與HashMap的關(guān)系來類比理解(即接口和實(shí)現(xiàn)類來的關(guān)系)。

下面來詳細(xì)說一下記憶集的具體實(shí)現(xiàn)卡表

卡表

卡表是使用一個(gè)字節(jié)數(shù)組CARD_TABLE[] 實(shí)現(xiàn),每個(gè)元素對(duì)應(yīng)其標(biāo)識(shí)的內(nèi)存區(qū)域一塊特定大小的內(nèi)存塊,每個(gè)內(nèi)存塊稱為卡頁(yè),hotspot使用的卡頁(yè)是2^9大小 即512字節(jié)。如下圖所示

Java的垃圾回收怎么理解

這樣我們就可以把某個(gè)區(qū)域按照卡頁(yè)進(jìn)行劃分,假如我們現(xiàn)在要對(duì)新生代區(qū)域進(jìn)行垃圾回收,那么就可以把老年代區(qū)域看成是一個(gè)卡頁(yè)一個(gè)卡頁(yè)劃分好的,如下圖所示。

Java的垃圾回收怎么理解

如圖所示,因?yàn)閏ardpage1中存在指向新生代的跨代引用,所以對(duì)應(yīng)卡表的第一個(gè)位置為1,表明該page區(qū)域存在跨代應(yīng)用的對(duì)象。

  • 卡表角度:因?yàn)閜age1中存在跨代飲用的對(duì)象,所以卡表對(duì)應(yīng)的第一個(gè)位置記為1,表明page1這個(gè)元素變臟。

  • 內(nèi)存回收角度:因?yàn)榭ū淼牡谝粋€(gè)位置為1,表明該page區(qū)域存在跨代應(yīng)用的對(duì)象,垃圾回收的時(shí)候需要掃描該區(qū)域。

一個(gè)卡頁(yè)的內(nèi)存中通常包含不止一個(gè)對(duì)象,只要卡頁(yè)內(nèi)有一個(gè)(或更多)對(duì)象的字段存在著跨代指針,那就將對(duì)應(yīng)卡表的數(shù)組元素的值標(biāo)識(shí)為1,稱為這個(gè)元素變臟(Dirty),沒有則標(biāo)識(shí)為0。在垃圾收集發(fā)生時(shí),只要篩選出卡表中變臟的元素,就能輕易得出哪些卡頁(yè)內(nèi)存塊中包含跨代指針,把它們加入GC Roots中一并掃描。這樣就不需要掃描整個(gè)老年代大大減少GC Roots的掃描范圍。 

以上就是關(guān)于“Java的垃圾回收怎么理解”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI