關注公衆號: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所示。
圖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可能存在的碎片。