由于虛擬機的分代實現,虛擬機不會考慮各個內存代如何實現垃圾回收,具體的工作(對象內存的分配也是一樣)由各內存代根據垃圾回收策略自行實現。
DefNewGeneration的使用復制算法進行回收。復制算法的思想是將eden和from區活躍的對象復制到to區,并清空eden區和from區,如果to區滿了,那么部分對象將會被晉升移動到老年代,隨后交換from和to區,即原來的to區存放了存活的對象作為新的from區存在,而from區被清空后當做新的to區而存在,移動次數超過一定閾值的對象也會被移動到老年代。
此外,在分析DefNewGeneration的垃圾回收之前,可以了解一下,在垃圾回收過程中,對對象的遍歷處理定義一個抽象基類OopClosure(對象表),并使用其不同的實現類來完成對對象的不同處理。
其中使用FastScanClosure來處理所有的根對象,FastEvacuateFollowersClosure處理所有的遞歸引用對象等。
在前文分析中,會調用各個內存代的collect()來完成垃圾回收。話不多說,直接上代碼:
DefNewGeneration::collect()定義在/hotspot/src/share/vm/memory/defNewGeneration.cpp中 。
1.當Survivor區空間不足,從Eden區移動過來的對象將會晉升到老年代,然而當老年代空間不足時,那么垃圾回收就是不安全的,將直接返回。
if (!collection_attempt_is_safe()) { if (Verbose && PRintGCDetails) { gclog_or_tty->print(" :: Collection attempt not safe :: "); } gch->set_incremental_collection_failed(); // Slight lie: we did not even attempt one return; }
collection_attempt_is_safe()判斷垃圾回收是否安全有以下判定條件:
bool DefNewGeneration::collection_attempt_is_safe() { if (!to()->is_empty()) { return false; } if (_next_gen == NULL) { GenCollectedHeap* gch = GenCollectedHeap::heap(); _next_gen = gch->next_gen(this); assert(_next_gen != NULL, "This must be the youngest gen, and not the only gen"); } return _next_gen->promotion_attempt_is_safe(used());}
(1).To區非空,則可能有不夠充足的轉移空間
(2).調用下一個內存代的promotion_attempt_is_safe()進行判斷,是否有充足的空間容納新生代的所有對象
2.一些準備工作
(1).統計堆的使用空間大小(僅留作輸出,可以不管)
(2).準備IsAliveClosure、ScanWeakRefClosure。
IsAliveClosure is_alive(this);ScanWeakRefClosure scan_weak_ref(this);
(3).清空ageTable和to區。
age_table()->clear();to()->clear(SpaceDecorator::Mangle);
(4).在初始化堆的過程,會創建一個覆蓋整個空間的數組GenRemSet,數組每個字節對應于堆的512字節,用于遍歷新生代和老年代空間,這里對GenRemSet進行初始化準備。
gch->rem_set()->prepare_for_younger_refs_iterate(false);
(5).準備FastEvacuateFollowersClosure。
FastScanClosure fsc_with_no_gc_barrier(this, false); FastScanClosure fsc_with_gc_barrier(this, true); set_promo_failure_scan_stack_closure(&fsc_with_no_gc_barrier); FastEvacuateFollowersClosure evacuate_followers(gch, _level, this, &fsc_with_no_gc_barrier, &fsc_with_gc_barrier);
3.調用GenCollectedHeap的gen_process_strong_roots()將當前代上的根對象復制到轉移空間中。
gch->gen_process_strong_roots(_level, true, // Process younger gens, if any, // as strong roots. true, // activate StrongRootsScope false, // not collecting perm generation. SharedHeap::SO_AllClasses, &fsc_with_no_gc_barrier, true, // walk *all* scavengable nmethods &fsc_with_gc_barrier);
4.遞歸處理根集對象的引用對象。
// "evacuate followers". evacuate_followers.do_void();
5.處理發現的引用。
FastKeepAliveClosure keep_alive(this, &scan_weak_ref); ReferenceProcessor* rp = ref_processor(); rp->setup_policy(clear_all_soft_refs); rp->process_discovered_references(&is_alive, &keep_alive, &evacuate_followers, NULL);
6.若沒有發生晉升失敗:
(1).那么此刻eden區和from區的對象應該已經全部轉移了,將調用clear()情況這兩片內存區域 。
if (!promotion_failed()) { // Swap the survivor spaces. eden()->clear(SpaceDecorator::Mangle); from()->clear(SpaceDecorator::Mangle);
(2).交換from和to區域,為下次gc做準備。
swap_spaces();
swap_spaces只是交換了_from_space和_to_space的起始地址,并設置eden的下一片需要進行壓縮的區域為現在的from區(與TenuredGeneration的標記-壓縮-清理垃圾回收相關,用來標志各內存區的壓縮順序),即原來的to區,而新的from區的下一片需要進行壓縮的區域為為NULL。
void DefNewGeneration::swap_spaces() { ContiguousSpace* s = from(); _from_space = to(); _to_space = s; eden()->set_next_compaction_space(from()); // The to-space is normally empty before a compaction so need // not be considered. The exception is during promotion // failure handling when to-space can contain live objects. from()->set_next_compaction_space(NULL); //...}
(3).計算新的survior區域的對象進入老年代的經歷的MinorGC次數閾值。
// Set the desired survivor size to half the real survivor space _tenuring_threshold = age_table()->compute_tenuring_threshold(to()->capacity()/HeapWordSize);
(4).當gc成功,會重新計算gc超時的時間計數。
AdaptiveSizePolicy* size_policy = gch->gen_policy()->size_policy(); size_policy->reset_gc_overhead_limit_count();
7.若發生了晉升失敗,即老年代沒有足夠的內存空間用以存放新生代所晉升的對象:
(1).恢復晉升失敗對象的markOop(被標記的活躍對象的markword內容為轉發指針,指向經過復制后對象的新地址)。
remove_forwarding_pointers();
remove_forwarding_pointers()會調用RemoveForwardPointerClosure對eden和from區內的對象進行遍歷,RemoveForwardPointerClosure將調用其do_object()初始化eden和from區所有對象的對象頭部分。
void DefNewGeneration::remove_forwarding_pointers() { RemoveForwardPointerClosure rspc; eden()->object_iterate(&rspc); from()->object_iterate(&rspc); //...assert while (!_objs_with_preserved_marks.is_empty()) { oop obj = _objs_with_preserved_marks.pop(); markOop m = _preserved_marks_of_objs.pop(); obj->set_mark(m); } _objs_with_preserved_marks.clear(true); _preserved_marks_of_objs.clear(true);
在晉升失敗處理的handle_promotion_failure()中,會將晉升失敗對象以<oop, markOop>作為一對分別保存在_objs_with_preserved_marks和_preserved_marks_of_objs棧中,這里就會恢復晉升失敗對象的對象頭,并清除這兩個棧。
(2).仍然需要交換from和to區域,設置from的下一片需要進行壓縮的區域為to
swap_spaces(); from()->set_next_compaction_space(to());
當沒有晉升失敗是,gc成功,會清空eden和from區、交換from和to區、survivor區對象成熟閾值調整等,以準備下次gc;而當晉升失敗時,雖然會在后面交換from和to區,但是并不會清空eden和from區,而是會清空eden和from區所有對象的對象頭,而只恢復晉升失敗部分的對象頭(加上to區的部分就是全部活躍對象了),這樣,在隨后觸發的FullGC中能夠對From和To區進行壓縮處理。
(3).設置堆的MinorGC失敗標記,并通知老年代(更高的內存代)晉升失敗,比如在ConcurrentMarkSweepGeneration會根據配置進行dump輸出以供JVM問題診斷
gch->set_incremental_collection_failed(); // Inform the next generation that a promotion failure occurred. _next_gen->promotion_failure_occurred();
8.設置from和to區域的并發遍歷指針的安全值為碰撞指針所在位置,并更新堆的最后一次gc的時間
from()->set_concurrent_iteration_safe_limit(from()->top()); to()->set_concurrent_iteration_safe_limit(to()->top()); SpecializationStats::print(); update_time_of_last_gc(os::javaTimeMillis());
下面將分別對根集對象標記、活躍對象標記、引用處理進行分析:
在分析gen_process_strong_roots()之前,首先看下處理函數會做哪些工作:
處理函數封裝在之前構造的FastScanClosure中,而FastScanClosure的do_oop()調用了的工作函數do_oop_work()。讓我們看看do_oop_work()究竟做了什么。
(1).這里使用模板函數來解決壓縮指針的不同類型(實際的oop和壓縮指針narrowOop)問題,并當對象非空時,獲取該oop/narrowOop對象(narrowOop需要進行指針解壓)
T heap_oop = oopDesc::load_heap_oop(p); // Should we copy the obj? if (!oopDesc::is_null(heap_oop)) { oop obj = oopDesc::decode_heap_oop_not_null(heap_oop);
(2).若該對象在遍歷區域內(_boudary是在FastScanClosure初始化的時候,為初始化時指定代的結束地址,與當前遍歷代的起始地址_gen_boundary共同作為對象的訪問邊界,故新生代DefNewGeneration會將其自身內存代和更低的內存代的活躍對象都標記復制到to區域中),若該對象沒有被標記過,即其標記狀態不為marked_value,就會將該對象復制到to區域內,隨后根據是否使用指針壓縮將新的對象地址進行壓縮
if ((HeapWord*)obj < _boundary) { assert(!_g->to()->is_in_reserved(obj), "Scanning field twice?"); oop new_obj = obj->is_forwarded() ? obj->forwardee() : _g->copy_to_survivor_space(obj); oopDesc::encode_store_heap_oop_not_null(p, new_obj); }
copy_to_survivor_space()的過程如下:
當該對象占用空間小于應當直接移動到老年代的閾值時,就會將其分配到to區
size_t s = old->size(); oop obj = NULL; // Try allocating obj in to-space (unless too old) if (old->age() < tenuring_threshold()) { obj = (oop) to()->allocate(s); }
否則會嘗試將該對象晉升,若晉升失敗,則調用handle_promotion_failure()處理
if (obj == NULL) { obj = _next_gen->promote(old, s); if (obj == NULL) { handle_promotion_failure(old); return old; } }
將原對象的數據內容復制到to區域新分配的對象上,并增加該對象的復制計數和更新ageTable (Prefetch使用的是目標架構的prefetch指令,用于將指定地址和長度的內存預取到cache,用于提升存取性能)
else { // Prefetch beyond obj const intx interval = PrefetchCopyIntervalInBytes; Prefetch::write(obj, interval); // Copy obj Copy::aligned_disjoint_words((HeapWord*)old, (HeapWord*)obj, s); // Increment age if obj still in new generation obj->incr_age(); age_table()->add(obj, s); }
最后調用forward_to()設置原對象的對象頭為轉發指針(表示該對象已被復制,并指明該對象已經被復制到什么位置)
// Done, insert forward pointer to obj in this header old->forward_to(obj);
接下來分析gen_process_strong_roots():
現在考慮一個問題:我們知道,被根對象所觸及的所有對象都是活躍對象,那么如何確定一個內存代中的活躍對象呢?或者換個思路,內存代中哪些對象是不可觸及的垃圾對象呢?如果其他內存代沒有指向該對象的引用并且該對象也沒有被內存代內其他對象引用,那么該對象就是一個垃圾對象。據此,把內存代內活躍對象的處理分為兩步:第一步,將內存代內正常的根對象和其他內存代內直接引用的內存代內的對象移動到To區域,這些對象作為活躍對象(雖然其他內存代的對象可能在下次Full GC成為垃圾對象,但顯然Minor GC顯然不能將這些對象當做垃圾對象),這樣,活躍對象的引用判斷范圍就縮小到了當前內存代,內存代內剩下的對象只要不是被這些活躍對象所引用,那么就必然是垃圾對象了;第二步,遞歸遍歷這些對象,將其所引用的在該內存代的對象移動到To區域。最終,剩下的對象就是垃圾對象了。
1.調用SharedHeap的process_strong_roots()處理根集對象,在當前內存代(新生代的eden和from區)的根集對象將會被復制到to區
if (!do_code_roots) { SharedHeap::process_strong_roots(activate_scope, collecting_perm_gen, so, not_older_gens, NULL, older_gens); } else { bool do_code_marking = (activate_scope || nmethod::oops_do_marking_is_active()); CodeBlobToOopClosure code_roots(not_older_gens, /*do_marking=*/ do_code_marking); SharedHeap::process_strong_roots(activate_scope, collecting_perm_gen, so, not_older_gens, &code_roots, older_gens); }
結合FastScanClosure可知,process_strong_roots()主要將當前內存代上的正常根對象復制到To區域。
2.處理更低的內存代
if (younger_gens_as_roots) { if (!_gen_process_strong_tasks->is_task_claimed(GCH_PS_younger_gens)) { for (int i = 0; i < level; i++) { not_older_gens->set_generation(_gens[i]); _gens[i]->oop_iterate(not_older_gens); } not_older_gens->reset_generation(); } }
(1).內存代的oop_iterate()是調用space_iterate()對該內存代的內存空間進行遍歷
//定義在/hotspot/src/share/vm/memory/generation.cpp中void Generation::oop_iterate(OopClosure* cl) { GenerationOopIterateClosure blk(cl, _reserved); space_iterate(&blk);}
(2).space_iterate()由Generation的實現類重寫,以OneContigSpaceCardGeneration為例(后面會處理更高的內存代,這里DefNewGeneration并沒有更低的內存代),將遍歷代上的內存空間。
void OneContigSpaceCardGeneration::space_iterate(SpaceClosure* blk, bool usedOnly) { blk->do_space(_the_space);}
(3).GenerationOopIterateClosure的do_space()如下:
virtual void do_space(Space* s) { s->object_iterate(_cl); }
(4).space的oop_iterate()根據Eden和from/to的實現如下:
void ContiguousSpace::oop_iterate(MemRegion mr, OopClosure* blk) { //... HeapWord* obj_addr = block_start(mr.start()); HeapWord* t = mr.end(); // Handle first object specially. oop obj = oop(obj_addr); SpaceMemRegionOopsIterClosure smr_blk(blk, mr); obj_addr += obj->oop_iterate(&smr_blk); while (obj_addr < t) { oop obj = oop(obj_addr); assert(obj->is_oop(), "expected an oop"); obj_addr += obj->size(); // If "obj_addr" is not greater than top, then the // entire object "obj" is within the region. if (obj_addr <= t) { obj->oop_iterate(blk); } else { // "obj" extends beyond end of region obj->oop_iterate(&smr_blk); break; } };}
該函數的作用是遍歷該區域的起始地址到空閑分配指針之間的所有對象,并調用對象的oop_iterate()進行處理。
(5).oop是在堆上的對象的基類型,其oop_iterate()調用了Klass的oop_oop_iterate##nv_suffix()
inline int oopDesc::oop_iterate(OopClosureType* blk) { / SpecializationStats::record_call(); / return blueprint()->oop_oop_iterate##nv_suffix(this, blk); /}
(6).oop_oop_iterate##nv_suffix()由具體的Klass子類(如對象在堆上的實現instanceKlass)實現,以訪問和處理其所包含的引用對象
#define InstanceKlass_OOP_OOP_ITERATE_DEFN(OopClosureType, nv_suffix) / /int instanceKlass::oop_oop_iterate##nv_suffix(oop obj, OopClosureType* closure) { / SpecializationStats::record_iterate_call##nv_suffix(SpecializationStats::ik);/ /* header */ / if (closure->do_header()) { / obj->oop_iterate_header(closure); / } / InstanceKlass_OOP_MAP_ITERATE( / obj, / SpecializationStats:: / record_do_oop_call##nv_suffix(SpecializationStats::ik); / (closure)->do_oop##nv_suffix(p), / assert_is_in_closed_subset) / return size_helper(); /}
instanceKlass的OopMapBlock描述了在實例對象空間中一連串引用類型域的起始位置和數量,而InstanceKlass_OOP_MAP_ITERATE(是一個語句塊)會遍歷OopMapBlock的所有塊
#define InstanceKlass_OOP_MAP_ITERATE(obj, do_oop, assert_fn) /{ / /* Compute oopmap block range. The common case / is nonstatic_oop_map_size == 1. */ / OopMapBlock* map = start_of_nonstatic_oop_maps(); / OopMapBlock* const end_map = map + nonstatic_oop_map_count(); / if (UseCompressedOops) { / while (map < end_map) { / InstanceKlass_SPECIALIZED_OOP_ITERATE(narrowOop, / obj->obj_field_addr<narrowOop>(map->offset()), map->count(), / do_oop, assert_fn) / ++map; / } / } else { / while (map < end_map) { / InstanceKlass_SPECIALIZED_OOP_ITERATE(oop, / obj->obj_field_addr<oop>(map->offset()), map->count(), / do_oop, assert_fn) / ++map; / } / } /}
InstanceKlass_SPECIALIZED_OOP_ITERATE如下:
#define InstanceKlass_SPECIALIZED_OOP_ITERATE( / T, start_p, count, do_oop, / assert_fn) /{ / T* p = (T*)(start_p); / T* const end = p + (count); / while (p < end) { / (assert_fn)(p); / do_oop; / ++p; / } /}
其中T為所要處理的對象的指針或壓縮指針,start_p為OopMapBlock中引用域的起始地址,count為OopMapBlock中引用的數量,do_oop為引用的處理,assert_fn為斷言,該宏所定義的語句塊就是將對象引用域的引用調用FastScanClosure的do_oop_nv進行處理。
所以,對更低內存代的遍歷和處理就是把更低內存代的對象在DefNewGeneration內存代所引用的對象移動到To區域。
3.處理更高的內存代
for (int i = level+1; i < _n_gens; i++) { older_gens->set_generation(_gens[i]); rem_set()->younger_refs_iterate(_gens[i], older_gens); older_gens->reset_generation(); }
類似地,把更高內存代的對象在DefNewGeneration內存代所引用的對象移動到To區域。這樣就完成了第一步,將回收范圍限定在DefNewGeneration內存代內。
假設堆上有如圖所示的對象引用模型:其中深色對象為根對象,箭頭代表對象的引用關系,我們主要關注當前內存代(DefNewGeneration)的對象和其處理過程。
那么根集對象的處理將如下:
遍歷所有的根對象,將在DefNewGeneration的根對象復制到To區域中,其中橙色對象表示該根對象已被復制移動到To空間,其頭部為轉發指針:
將更高或更低內存代所引用的在DefNewGeneration中的對象復制到To區域中
在分析遞歸標記活躍對象的過程之前,不妨先了解一下遞歸標記所使用的cheney算法。
在廣優先遍歷掃描活躍對象的過程中,對于所需的遍歷隊列,將復用to的從空閑指針開始的一段空間作為隱式隊列。在之前,根集對象已經被拷貝到to區域的空閑空間,而scanned指針仍然停留在沒有復制根集對象時空閑指針的位置,即scanned指針到當前空閑分配指針(to()->top())的這段空間保存著已經標記的根集對象,所以只需要繼續遍歷這段空間的根集對象,將發現的引用對象復制到to區域后,讓scanned指針更新到這段空間的結束位置,而若還有未標記的對象的話,那么,空間指針必然又前進了一段距離,繼續遍歷這段新的未處理空間的對象,直至scanned指針追上空閑分配指針即可
FastEvacuateFollowersClosure的do_void()將完成遞歸標記工作:
當各分代的空閑分配指針不在變化時,說明所有可觸及對象都已經遞歸標記完成,否則,將調用oop_since_save_marks_iterate()進行遍歷標記。
void DefNewGeneration::FastEvacuateFollowersClosure::do_void() { do { _gch->oop_since_save_marks_iterate(_level, _scan_cur_or_nonheap, _scan_older); } while (!_gch->no_allocs_since_save_marks(_level)); guarantee(_gen->promo_failure_scan_is_complete(), "Failed to finish scan");}
1.循環條件oop_since_save_marks_iterate()是對當前代、更高的內存代以及永久代檢查其scanned指針_saved_mark_word是否與當前空閑分配指針位置相同,即檢查scanned指針是否追上空閑分配指針
bool GenCollectedHeap::no_allocs_since_save_marks(int level) { for (int i = level; i < _n_gens; i++) { if (!_gens[i]->no_allocs_since_save_marks()) return false; } return perm_gen()->no_allocs_since_save_marks();}
在DefNewGeneration中,eden和from區的分配指針不應當有所變化,只需要檢查to區的空閑分配指針位置是否變化即可
bool DefNewGeneration::no_allocs_since_save_marks() { assert(eden()->saved_mark_at_top(), "Violated spec - alloc in eden"); assert(from()->saved_mark_at_top(), "Violated spec - alloc in from"); return to()->saved_mark_at_top();}
2.循環處理oop_since_save_marks_iterate():
(1).oop_since_save_marks_iterate()是對當前代、更高的內存代以及永久代的對象遍歷處理
#define GCH_SINCE_SAVE_MARKS_ITERATE_DEFN(OopClosureType, nv_suffix) /void GenCollectedHeap:: /oop_since_save_marks_iterate(int level, / OopClosureType* cur, / OopClosureType* older) { / _gens[level]->oop_since_save_marks_iterate##nv_suffix(cur); / for (int i = level+1; i < n_gens(); i++) { / _gens[i]->oop_since_save_marks_iterate##nv_suffix(older); / } / perm_gen()->oop_since_save_marks_iterate##nv_suffix(older); /}
那么為什么要處理更高的內存代對象?因為在復制過程中,有對象通過晉升移動到了更高的內存代。
不過為什么老年代TenuredGeneration不像ConcurrentMarkSweepGeneration一樣維護一個晉升對象的鏈表PromotionInfo來加快晉升對象的處理呢?
oop_since_save_marks_iterate##nv_suffix()在DefNewGeneration中的定義如下,實際上是調用eden、to、from區的同名函數進行處理,并更新各區的空閑分配指針。
#define DefNew_SINCE_SAVE_MARKS_DEFN(OopClosureType, nv_suffix) / /void DefNewGeneration:: /oop_since_save_marks_iterate##nv_suffix(OopClosureType* cl) { / cl->set_generation(this); / eden()->oop_since_save_marks_iterate##nv_suffix(cl); / to()->oop_since_save_marks_iterate##nv_suffix(cl); / from()->oop_since_save_marks_iterate##nv_suffix(cl); / cl->reset_generation(); / save_marks(); /}
(2).之前說到,在空間分配指針到scanned指針之間的區域就是已分配但未掃描的對象,所以在這里將對這片區域內的對象調用遍歷函數進行處理,以標記遍歷的對象所引用的對象,并保存新的scanned指針。
#define ContigSpace_OOP_SINCE_SAVE_MARKS_DEFN(OopClosureType, nv_suffix) / /void ContiguousSpace:: /oop_since_save_marks_iterate##nv_suffix(OopClosureType* blk) { / HeapWord* t; / HeapWord* p = saved_mark_word(); / assert(p != NULL, "expected saved mark"); / / const intx interval = PrefetchScanIntervalInBytes; / do { / t = top(); / while (p < t) { / Prefetch::write(p, interval); / debug_only(HeapWord* prev = p); / oop m = oop(p); / p += m->oop_iterate(blk); / } / } while (t < top()); / / set_saved_mark_word(p); /
最后,將清理Eden和From區域,并交換From和To區域
1.處理_discoveredSoftRefs數組中的軟引用
// Soft references { TraceTime tt("SoftReference", trace_time, false, gclog_or_tty); process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true, is_alive, keep_alive, complete_gc, task_executor); } update_soft_ref_master_clock();
2.處理_discoveredWeakRefs數組中的弱引用
// Weak references { TraceTime tt("WeakReference", trace_time, false, gclog_or_tty); process_discovered_reflist(_discoveredWeakRefs, NULL, true, is_alive, keep_alive, complete_gc, task_executor); }
3.處理_discoveredFinalRefs數組中的Final引用
// Final references { TraceTime tt("FinalReference", trace_time, false, gclog_or_tty); process_discovered_reflist(_discoveredFinalRefs, NULL, false, is_alive, keep_alive, complete_gc, task_executor); }
4.處理_discoveredPhantomRefs列表中的影子引用
// Phantom references { TraceTime tt("PhantomReference", trace_time, false, gclog_or_tty); process_discovered_reflist(_discoveredPhantomRefs, NULL, false, is_alive, keep_alive, complete_gc, task_executor); }
5.處理JNI弱全局引用
{ TraceTime tt("JNI Weak Reference", trace_time, false, gclog_or_tty); if (task_executor != NULL) { task_executor->set_single_threaded_mode(); } process_phaseJNI(is_alive, keep_alive, complete_gc); }
process_discovered_reflist()過程比較復雜,這里就不繼續進行了,有興趣的可以自行分析,DefNewGeneration的GC流程圖如下:
新聞熱點
疑難解答