圖解 JVM 如何解決對象跨代引用的問題
在新生代做 GCRoots 可達性掃描過程中,可能會碰到跨代引用的對象的問題,如下圖所示:
在新生代掃面的時候會掃描不到 D 對象,如果這個 D 對象不去老年代掃描是否有被引用,那麼就會無法觸達,如下如所示:
但是 D 對象是有被引用的,如果直接回收 D 對象就會造成一些意想不到的問題,如果爲了掃描 D 對象是否存在跨代引用而對老年代整體掃描一遍,就會帶來整個垃圾回收的效率低下的問題。
爲了解決跨代引用的問題,可以在新生代可以引入 RememberSet(記錄從非收集區到收集區的指針集合)的數據結構,稱爲記憶集,這樣避免把整個老年代加入到 GCRoots 掃描範圍中,如下所示:
記憶集是一種概念,在 hotspot 使用名爲 “卡表”(Cardtable) 的方式實現記憶集,它也是目前最常用的一種方式。卡表使用一個字節數組來實現:CARD_TABLE[],每個元素對應着其標識的內存區域一塊特定大小的內存塊,我們稱之爲“卡頁”,如下圖所示:
將老年代按照一個卡頁大小(512 字節)分成 n 個區域,這個區域的地址信息都記錄到卡表中。當某個區域中出現跨代引用的時候,我們就在卡表中記錄信息,如下所示:
老年代的 F 對象引用了年輕代的 D 對象,那麼我們就在卡表中記錄 a 卡頁中有跨代引用(設置對應區域的值爲 1,如上 a=1;卡頁 b 沒有跨代引用,設置 b=0 來表示此區域無跨代引用)。
當年輕代做 GCRoots 掃描的時候,我們去卡表中查詢哪些區域存在跨代的對象,然後判斷這個對象是否還繼續存活,如下所示:
一個卡頁中可包含多個對象,只要有一個對象存在跨代引用,那麼其對應在卡表中的元素標識就設置成 1,表示該區域存在跨代引用,否則爲 0。GC 時只要篩選本收集區的卡表中爲 1 區域中的元素加入 GCRoots 裏(如上圖中 a=1 就被篩選出來做 GCRoots)。
通過卡表的方式我們就避免了大規模的掃描老年代對象,假設老年代有上萬的對象存活,年輕代之後幾十個存活對象,通過卡表我們只需要掃面少部分的老年代對象,大大的提升垃圾收集的效率。
在 hotspot 使用寫屏障維護卡表中數據狀態,當老年代引用了新生代的對象時候,底層會維護將卡表中的對應的區域設置爲 1。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/dKxqo4M8vopwrPrKTZP78g