垃圾收集之Remember Set(CardTable)
前言
JVM為了更好的管理內存,提高GC效率,一般都會對內存進行劃分,比如經典的分代收集,G1推出的Region等。而Java中的垃圾收集基本都基于可達性分析算法,這就涉及到對象枚舉遍歷和標記的過程。
在做Minor GC的時候會對年輕代進行根節點枚舉,但是如果年輕代中的一些對象被老年代引用著,那么在做年輕代可達性分析的時候就會遇到很大的阻礙,總不能在Minor GC的時候還去掃描老年代吧?這種跨代引用的問題拋開出現頻率,它總是會存在,而且在G1這種多Region結構中更加突出(包括ZGC、Shenandoah等)。虛擬機為了避免全堆掃描,便引入了Remember Set。
解釋
對于Remember Set,可以將其理解為一個抽象的數據結構,Hot Spot對它有一個叫做CardTable的"實現"?;\統的說,在HotSpot中CardTable是一個字節數組(jbyte*),數組中的每個元素稱為一個card。一個內存地址可以被映射到CardTable中的一個位置上,怎么映射呢?比如有一塊內存區域,現在從邏輯上將其按照固定大小進行劃分,就像這樣:
可以將這些各自想象成card,假設內存地址從0x0開始,那么可以簡單理解為從0x0開始到第一個范圍結束的地址都落在CardTable的第0個位置,后面依次類推,一個card可以包含多個對象。這個大小定義在cardTableModRefBS.hpp中:
enum SomePublicConstants {card_shift = 9,//card大小card_size = 1 << card_shift,card_size_in_words = card_size / sizeof(HeapWord)};可以看到card_size定義為2^9,也就是512字節。那么card又是如何使用的呢?以G1為例,G1中每個Region都有一個與之對應的CardTable,當在對Reference類型的數據進行寫操作時,會通過寫屏障(前面文章提到過了,這個和并發里的屏障不是一碼事,可以簡單理解為AOP)檢查Referene引用的對象是否處于不同的Region中,如果是的話,那么就以引用對象的地址為基礎,將其映射到被引用對象所在Region的CardTable中的”位置“,也就是映射到一個card,將其標記為dirty。這樣后面在枚舉被引用對象的Region的時候,把該Region的CardTble中被標記為dirty的card拿出來,加入到GC Roots的范圍中,這樣就避免了去掃描引用對象所在的Region。
所謂的標記為dirty,就是類似與對象頭中的鎖狀態標識,定義在枚舉中:
enum CardValues {clean_card = -1,// The mask contains zeros in places for all other values.clean_card_mask = clean_card - 31,dirty_card = 0,precleaned_card = 1,claimed_card = 2,deferred_card = 4,last_card = 8,CT_MR_BS_last_reserved = 16};標記一個card為dirty和判斷一個card是否被標識為dirty可以這樣簡單處理:
bool is_card_dirty(size_t card_index) {return _byte_map[card_index] == dirty_card_val();}void mark_card_dirty(size_t card_index) {_byte_map[card_index] = dirty_card_val();}其中dirty_card_val(),返回的就是枚舉中定義的dirty_card:
static int dirty_card_val() { return dirty_card; }當然,枚舉中還定義了一些其它值,用于標識card處理過程中的一些狀態。如果一個card被標識為dirty,那么在GC時就會掃描這個card,從而避免全堆掃描。可以看看在g1RemSet.cpp中的doHeapRegion函數的處理:
if (!card_region->in_collection_set() && !_ct_bs->is_card_dirty(card_index)) {scanCard(card_index, card_region); }總結
Remember Set主要是為了解決跨代引用對GC的影響,Hot Spot實際使用的是一個叫做CardTable的結構。對于G1來說,每個Region都有一個CardTable,每個CardTable都有若干大小為512字節的card用于存儲其它Region的對象地址。當Region A的一個對象A在引用Region B的一個對象B的時候,會通過寫屏障將A對象的地址映射到Region B的CardTable中,標識對應card為dirty,之后在對Region B進行根節點枚舉的時候,會把Region B的CardTable中被標記為dirty的card納入到根節點的范圍中進行掃描,以此避免去掃描Region A。
如果對應到分代的情況,那就是在新生代中記錄了老年代的一塊塊地址,在Minor GC的時候,直接從CardTable中獲取到需要掃描的老年代對象,而不用去掃描整個老年代。
注:源碼中關于CardTable的管理比較復雜,這里沒有去分析處理的流程,只是大概貼出了一些標志性代碼,以輔助理解~
總結
以上是生活随笔為你收集整理的垃圾收集之Remember Set(CardTable)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EMI整改经验小结
- 下一篇: “PMVDN”元宇宙新势力崛起