你必須明白的新生代垃圾回收:YoungGC

關注公衆號:Java架構師聯盟,每日更新技術好文算法

Young GC

前文提到,Young GC(如下簡稱YGC)是指新生代垃圾回收,下面將詳細討論G1的YGC過程。架構

選擇CSetoop

YGC的回收過程位於 G1CollectedHeap::do_collection_pause_at_safepoint(),在進行垃圾回收前它會建立一個清理集CSet(Collection Set),存放須要被清理的Region。選擇合適的Region放入CSet是爲了讓G1達到用戶指望的合理的停頓時間。CSet的建立過程如代碼清單11-2所示:spa

代碼清單11-2 選擇Region放入CSet線程

void G1Policy::finalize_collection_set(...) {
// 先選擇新生代Region,用戶指望的最大停頓時間是target_pause_time_ms
// G1計算出清理新生代Region的可能用時後,會將剩下的時間(time_remaining_ms)給老年代
double time_remaining_ms =
_collection_set->finalize_young_part(...);
_collection_set->finalize_old_part(time_remaining_ms);
}

G1的YGC只負責清理新生代Region,所以finalize_old_part()不會選擇任何Region,因此只須要關注finalize_young_part()。finalize_young_part會在將全部Eden和Survivor Region加入CSet後準備垃圾回收。code

G1在evacuate_collect_set()中建立G1ParTask,而後阻塞,直到G1ParTask執行完成,這意味着整個YGC期間應用程序是STW的。相似Parallel GC的YGC,G1ParTask的執行由線程組GangWorker完成,以儘可能減小STW時間。不難看出,YGC的實際工做位於G1ParTask,它主要分爲三個階段:對象

1)清理根集( G1RootProcessor::evacuate_roots);隊列

2)處理RSet( G1RemSet::oops_into_collection_set_do);rem

3)對象複製( G1ParEvacuateFollowersClosure::do_void)。get

清理根集

第一階段是清理根集。第10章提到HotSpot VM不少地方都屬於GCRoot,G1ParTask的evacuate_roots()會從這些GC Root出發尋找存活對象。以線程棧爲例,G1會掃描虛擬機全部JavaThread和VMThread的線程棧中的每個棧幀,找到其中的對象引用,並對它們應用G1ParCopyClosure,如代碼清單11-3所示:

代碼清單11-3 G1ParCopyClosure

void G1ParCopyClosure<barrier, do_mark_object>::do_oop_work(T* p) {
...
oop obj = CompressedOops::decode_not_null(heap_oop);const InCSetState state = _g1h->in_cset_state(obj);
// 若是對象屬於CSet
if (state.is_in_cset()) {
oop forwardee;
markOop m = obj->mark_raw();
if (m->is_marked()) { // 若是已經複製過則直接返回複製後的新地址
forwardee = (oop) m->decode_pointer();
} else { // 將它複製到Survivor Region,返回新地址
forwardee = _par_scan_state->copy_to_survivor_space(...);
}
// 修改根集中指向該對象的引用,指向Survivor中複製後的對象
RawAccess<IS_NOT_NULL>::oop_store(p, forwardee);
...
} else {
...
}
}

清理根集的核心代碼是copy_to_survivor_space,它將Eden Region中年齡小於15的對象移動到Survivor Region,年齡大於等於15的對象移動到Old Region。以前根集中的引用指向Eden Region對象,對這些引用應用G1ParCopyClosure以後,Eden Region的對象會被複制到SurvivorRegion,因此根集的引用也須要相應改變指向,如圖11-3所示。

img

圖11-3 清理根集

copy_to_survivor_space在移動對象後還會用G1ScanEvacuatedObjClosure處理對象的成員,若是成員也屬於CSet,則將它們放入一個G1ParScanThreadState隊列,等待第三階段將它們複製到Survivor Region。總結來講,第一階段會將根集直接可達的對象複製到Survivor Region,並將這些對象的成員放入隊列,而後更新根集指向。

處理RSet

第一階段標記了從GC Root到Eden Region的對象,對於從OldRegion到Eden Region的對象,則須要藉助RSet,這一步由G1ParTask的 G1RemSet::oops_into_collection_set_do完成,它包括更新RSet(update_rem_set)和掃描RSet(scan_rem_set)兩個過程。

scan_rem_set遍歷CSet中的全部Region,找到引用者並將其做爲起點開始標記存活對象。

對象複製

通過前面的步驟後,YGC發現的全部存活對象都會位於G1ParScanThreadState隊列。對象複製負責將隊列中的全部存活對象複製到Survivor Region或者晉升到Old Region,如代碼清單11-4所示:

代碼清單11-4 對象複製

template <class T> void G1ParScanThreadState::do_oop_evac(T* p) {
// 只複製位於CSet的存活對象
oop obj = RawAccess<IS_NOT_NULL>::oop_load(p);
const InCSetState in_cset_state = _g1h->in_cset_state(obj);
if (!in_cset_state.is_in_cset()) {
return;
}
// 將對象複製到Survivor Region(或晉升到Old Region)
markOop m = obj->mark_raw();
if (m->is_marked()) {
obj = (oop) m->decode_pointer();
} else {
obj = copy_to_survivor_space(in_cset_state, obj, m);
}
RawAccess<IS_NOT_NULL>::oop_store(p, obj);
// 若是複製後的Region和複製前的Region相同,直接返回
if (HeapRegion::is_in_same_region(p, obj)) {
return;}
// 若是複製前Region是老年代,如今複製到Survivor/Old Region,
// 則會產生跨代引用,須要更新RSet
HeapRegion* from = _g1h->heap_region_containing(p);
if (!from->is_young()) {
enqueue_card_if_tracked(p, obj);
}
}

對象複製是YGC的最後一步,在這以後新生代全部存活對象都被移動到Survivor Region或者晉升到Old Region,以前的Eden空間能夠被回收(Reclaim)。另外,YGC複製算法至關於作了一次堆碎片的清理工做,如整理Eden Region可能存在的碎片。

相關文章
相關標籤/搜索