我正在閱讀有關Java中垃圾收集的一些材料,以深入瞭解GC過程中真正發生的事情。卡片表和作家屏障實際上是如何工作的?
我遇到了名爲「卡表」的機制。我谷歌搜索了它,並沒有找到全面的信息。大部分解釋都很淺,並且描述得像一些魔法。
我的問題是:如何卡表和寫屏障的作品?卡表中標記了什麼?那麼垃圾收集器如何知道這個特定的對象是被另一個對象引用的,而這個對象是舊一代存儲的。
我想對這個機制有一些實際的想象,就像我本來應該準備一些模擬一樣。
我正在閱讀有關Java中垃圾收集的一些材料,以深入瞭解GC過程中真正發生的事情。卡片表和作家屏障實際上是如何工作的?
我遇到了名爲「卡表」的機制。我谷歌搜索了它,並沒有找到全面的信息。大部分解釋都很淺,並且描述得像一些魔法。
我的問題是:如何卡表和寫屏障的作品?卡表中標記了什麼?那麼垃圾收集器如何知道這個特定的對象是被另一個對象引用的,而這個對象是舊一代存儲的。
我想對這個機制有一些實際的想象,就像我本來應該準備一些模擬一樣。
我不知道你是否發現了一些格外糟糕的描述,或者你是否期望太多細節,我對explanations I've seen相當滿意。如果描述簡潔而簡單,那是因爲它確實是一個相當簡單的機制。
正如你顯然已經知道的,世代垃圾收集器需要能夠枚舉引用年輕對象的舊對象。掃描所有舊物體是正確的,但這破壞了世代方法的優點,所以你必須縮小它的範圍。無論你如何做,都需要一個寫屏障 - 每當成員變量(引用類型的)被分配/寫入時執行一段代碼。如果新引用指向一個年輕對象並且它存儲在一箇舊對象中,則寫入屏障會記錄垃圾收集的事實。區別在於它如何記錄。有確切的方案使用所謂的記憶集合,每一個的集合,它具有(在某個時間點)對年輕對象的引用。你可以想象,這需要相當多的空間。
牌桌上是一個權衡:不是告訴你哪些對象正是包含年輕指針(或至少沒有在某個時候),這組對象爲固定大小的水桶和跟蹤哪些桶包含對象與年輕的指針。這當然會減少空間使用量。爲了保持正確性,只要你對這些對象保持一致,你如何挖掘這些對象並不重要。爲了提高效率,您只需按照內存地址對它們進行分組(因爲您可以免費獲得),然後再除以2的較大冪數(以使分區成爲便宜的按位操作)。
此外,不是維護一個明確的桶列表,而是爲每個可能的桶提前預留一些空間。具體來說,有一個N位或字節數組,其中N是桶的數量,因此如果i
th桶不包含年輕指針,則值爲0,如果它包含年輕指針,則值爲1。這是卡表正確的。通常,這個空間被分配並釋放,同時還有一大堆作爲堆的一部分的內存。如果不需要增長的話,它甚至可以嵌入到內存塊的開始部分。除非整個地址空間被用作堆(這是非常罕見的),否則上面的公式給出的數字從start_of_memory_region >> K
開始,而不是0,因此要獲取卡表中的索引,必須減去堆起始地址的開始。
總之,當寫屏障認爲該聲明some_obj.field = other_obj;
保存在一箇舊對象一個年輕的指針,它這樣做:
card_table[(&old_obj - start_of_heap) >> K] = 1;
哪裏&old_obj
的是,現在有一個年輕的指針對象的地址(它已經在註冊表中,因爲它只是決定引用一箇舊對象)。 在輕微GC,垃圾收集器看起來在牌桌上,以確定掃描年輕的指針,其堆地區:
for i from 0 to (heap_size >> K):
if card_table[i]:
scan heap[i << K .. (i + 1) << K] for young pointers
早前我寫了一篇文章,解釋在熱點JVM年輕收集的機制。 Understanding GC pauses in JVM, HotSpot's minor GC
髒卡寫障礙原理非常簡單。每當程序修改內存中的引用時,它應該將修改後的內存頁面標記爲髒。 JVM中有一個特殊的卡表,每個512字節的內存頁面在卡表中都有一個字節的輸入。
通常情況下,從舊空間到年輕人的所有引用的收集都需要掃描舊空間中的所有對象。這就是爲什麼我們需要書寫障礙。年輕空間中的所有對象自上次重置寫屏障以來都已創建(或重新定位),因此非髒頁面不能引用年輕空間。這意味着我們只能掃描髒頁面中的對象。
要進入這麼深的理論,你可以嘗試一下這本書http://gchandbook.org/contents.html。它提供了安靜全面的概述。 – Mikhail