簡書 占小狼
轉(zhuǎn)載請注明原創(chuàng)出處氛堕,謝謝
在上篇文章《JVM源碼分析之YGC的來龍去脈》中提到了跨代引用dirty card弓柱,不過只是一帶而過队萤,細心的同學表示對這一塊不理解丸卷,當時也是考慮到短篇幅內(nèi)無法講清楚跨代引用中的細節(jié)蔚出,所以準備在本文中進行分析汹粤,不過這篇文章涉及的內(nèi)容過于冷門射亏,估計感興趣的同學不會很多近忙。
思考
先思考一個問題,在進行YGC時智润,如果young generation的Y對象被old generation中O對象引用及舍,那么稱O對象存在跨代引用,而且Y對象應該在本次垃圾回收中存活下來窟绷,所以old generation的對象在YGC時也是Strong root的一部分击纬,如果每次YGC都去掃描old generation中所有對象的話,肯定會非常耗時钾麸,那么有什么好的解決方案呢更振?
如果只掃描那些有young generation對象引用的對象,是不是效率可以達到最高饭尝,不過使用這種方式肯腕,需要有一個地方保存這些對象的引用,是一個不小的內(nèi)存開銷钥平,所以Hotspot實現(xiàn)中实撒,并沒采用這樣方式姊途,而是使用一個GenRemSet數(shù)據(jù)結(jié)構(gòu),記錄包含這些對象的內(nèi)存區(qū)域是clean or dirty狀態(tài)知态。
CardTable
CardTable是GenRemSet的一種實現(xiàn)捷兰,類似于一個數(shù)組,每個元素對應著堆內(nèi)存的一塊區(qū)域是否存在跨代引用的對象负敏,如果存在贡茅,該Card為dirty狀態(tài)。
GenRemSet隨著堆內(nèi)存一起初始化其做,通過具體的垃圾收集策略進行創(chuàng)建顶考,比如CMS和G1是不一樣的,其中CMS對應的是CardTable妖泄。
CardTableRS
實例化過程如下:
其核心實現(xiàn)都在CardTableModRefBSForCTRS
中驹沿,有點復雜。
接上文中YGC遍歷old generation的邏輯
rem_set()->younger_refs_iterate(_gens[i], older_gens);
這里rem_set()方法返回的就是已經(jīng)初始化的CardTableRS對象蹈胡,調(diào)用younger_refs_iterate
渊季,傳入的參數(shù)分別是old generation的引用和負責遍歷old generation對象的回調(diào)函數(shù)FastScanClosure
,一步一步調(diào)用下去罚渐,最終調(diào)用到ClearNoncleanCardWrapper::do_MemRegion
方法
其中參數(shù)MemRegion相當于堆內(nèi)存的一塊區(qū)域却汉,這里指向old generation從_bottom 到 _top的區(qū)間。
紅色框的代碼分析:
_ct->byte_for(mr.last())
計算old generation最大地址對應的Card cur_entry
_ct->byte_for(mr.start())
計算old generation最小地址對應的Card limit
byte_for函數(shù)實現(xiàn)如下:
其中card_shift
為9搅轿,相當于整個地址大小除以 512,可以看成一個Card對應512字節(jié)大小富玷。
綠色框的代碼分析:
從最大的Card開始璧坟,一直遍歷到最小的Card
HeapWord* cur_hw = _ct->addr_for(cur_entry);
if ((*cur_entry != CardTableRS::clean_card_val()) && clear_card(cur_entry)) {
// Continue the dirty range by opening the
// dirty window one card to the left.
start_of_non_clean = cur_hw;
}
如果當前的Card不是clean_card狀態(tài),說明該Card對應的內(nèi)存區(qū)域至少有一個對象引用著young generation的對象赎懦,通過clear_card
方法先設置該Card為clean_card狀態(tài)雀鹃,然后記錄當前的內(nèi)存區(qū)域的開始地址到start_of_non_clean
字段,說明從start_of_non_clean
開始到上次記錄的 end_of_non_clean
之間的對象都需要掃描励两;
黃色框的代碼分析:
if (start_of_non_clean < end_of_non_clean) {
const MemRegion mrd(start_of_non_clean, end_of_non_clean);
_dirty_card_closure->do_MemRegion(mrd);
}
在while循環(huán)中黎茎,如果遇到一個clean_card,則先處理已經(jīng)找到的dirty card当悔,掃描start_of_non_clean
到end_of_non_clean
之間的對象傅瞻,整個掃描過程和掃描to-space的邏輯差不多,只有一個區(qū)別盲憎,主要體現(xiàn)在_gc_barrier
變量
掃描old generation對象時的回調(diào)函數(shù)FastScanClosure嗅骄,_gc_barrier
初始化時候為 true,每次都會觸發(fā)do_barrier
方法饼疙,實現(xiàn)如下:
如果拷貝之后的對象還在young generation溺森,則執(zhí)行inline_write_ref_field_gc
重新把對應的Card設置為dirty。
每次的動作是先清除Card的dirty狀態(tài),對象拷貝完成再判斷是否要設置為dirty屏积,即非clean医窿。