圖解JVM如何解決對(duì)象跨代引用的問(wèn)題
在新生代做GCRoots可達(dá)性?huà)呙柽^(guò)程中,可能會(huì)碰到跨代引用的對(duì)象的問(wèn)題,如下圖所示:
圖片
在新生代掃面的時(shí)候會(huì)掃描不到D對(duì)象,如果這個(gè)D對(duì)象不去老年代掃描是否有被引用,那么就會(huì)無(wú)法觸達(dá),如下如所示:
圖片
但是D對(duì)象是有被引用的,如果直接回收D對(duì)象就會(huì)造成一些意想不到的問(wèn)題,如果為了掃描D對(duì)象是否存在跨代引用而對(duì)老年代整體掃描一遍,就會(huì)帶來(lái)整個(gè)垃圾回收的效率低下的問(wèn)題。
為了解決跨代引用的問(wèn)題,可以在新生代可以引入RememberSet(記錄從非收集區(qū)到收集區(qū)的指針集合)的數(shù)據(jù)結(jié)構(gòu),稱(chēng)為記憶集,這樣避免把整個(gè)老年代加入到GCRoots掃描范圍中,如下所示:
圖片
記憶集是一種概念,在hotspot使用名為“卡表”(Cardtable)的方式實(shí)現(xiàn)記憶集,它也是目前最常用的一種方式。卡表使用一個(gè)字節(jié)數(shù)組來(lái)實(shí)現(xiàn):CARD_TABLE[],每個(gè)元素對(duì)應(yīng)著其標(biāo)識(shí)的內(nèi)存區(qū)域一塊特定大小的內(nèi)存塊,我們稱(chēng)之為“卡頁(yè)”,如下圖所示:
圖片
將老年代按照一個(gè)卡頁(yè)大?。?12字節(jié))分成n個(gè)區(qū)域,這個(gè)區(qū)域的地址信息都記錄到卡表中。當(dāng)某個(gè)區(qū)域中出現(xiàn)跨代引用的時(shí)候,我們就在卡表中記錄信息,如下所示:
圖片
老年代的F對(duì)象引用了年輕代的D對(duì)象,那么我們就在卡表中記錄a卡頁(yè)中有跨代引用(設(shè)置對(duì)應(yīng)區(qū)域的值為1,如上a=1;卡頁(yè)b沒(méi)有跨代引用,設(shè)置b=0來(lái)表示此區(qū)域無(wú)跨代引用)。
當(dāng)年輕代做GCRoots掃描的時(shí)候,我們?nèi)タū碇胁樵?xún)哪些區(qū)域存在跨代的對(duì)象,然后判斷這個(gè)對(duì)象是否還繼續(xù)存活,如下所示:
圖片
一個(gè)卡頁(yè)中可包含多個(gè)對(duì)象,只要有一個(gè)對(duì)象存在跨代引用,那么其對(duì)應(yīng)在卡表中的元素標(biāo)識(shí)就設(shè)置成1,表示該區(qū)域存在跨代引用,否則為0。GC時(shí)只要篩選本收集區(qū)的卡表中為1區(qū)域中的元素加入GCRoots里(如上圖中a=1就被篩選出來(lái)做GCRoots)。
通過(guò)卡表的方式我們就避免了大規(guī)模的掃描老年代對(duì)象,假設(shè)老年代有上萬(wàn)的對(duì)象存活,年輕代之后幾十個(gè)存活對(duì)象,通過(guò)卡表我們只需要掃面少部分的老年代對(duì)象,大大的提升垃圾收集的效率。
在hotspot使用寫(xiě)屏障維護(hù)卡表中數(shù)據(jù)狀態(tài),當(dāng)老年代引用了新生代的對(duì)象時(shí)候,底層會(huì)維護(hù)將卡表中的對(duì)應(yīng)的區(qū)域設(shè)置為1。