我们都知道当新生代剩下的空间不够分配会触发GC垃圾回收,新生代的GC是对部分内存进行垃圾回收,GC时间比较少,分区化的G1堆针对新生代的收集的内存也是不固定的。首先我们明白在进行YGC的时候会进行STW。然后会选择需要收集的CSet,针对新生代而言就是整个新生代分区。然后加入收集任务中,去并行处理引用。引用关系搜索完毕之后,就是进行对象引用回收,处理对象晋升,晋升失败的还原对象头,尝试扩展内存等。G1-YGC工作流程如下
do_collection_pause_at_safepoint直接进入CollectedHeap.cpp#evacuate_collection_set方法一探其究。下图为并行清理CSet方法的工作流程
[图片上传中...(image-9d4305-1675644518441-3)]
-
使用G1RootProcessor类去执行根扫描,扫描直接强引用。主要是JVM根和Java根。使用G1ParCopyHelper把对象复制。
-
Java根
-
类加载器
深度遍历当前类的加载的所有存活的Klass对象,找到之后复制到Survivor区或者晋升老年代。
-
线程栈
处理Java线程栈和本地方法栈中找,通过
StackFrameStream
的next执行飞到Sender,从而得到调用者,进而其找到关联的活跃堆内对象,将其复制到Survivor区或者晋升老年代。
知道了G1RootProcessor类会从上述的两个大方向上去找活跃对象,那么直接看代码,g1RootProcessor.cpp#evacuate_roots
void G1RootProcessor::process_java_roots(OopClosure* strong_roots, CLDClosure* thread_stack_clds, CLDClosure* strong_clds, CLDClosure* weak_clds, CodeBlobClosure* strong_code, G1GCPhaseTimes* phase_times, uint worker_i) { assert(thread_stack_clds == NULL || weak_clds == NULL, "There is overlap between those, only one may be set"); // Iterating over the CLDG and the Threads are done early to allow us to // first process the strong CLDs and nmethods and then, after a barrier, // let the thread process the weak CLDs and nmethods. { G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::CLDGRoots, worker_i); if (!_process_strong_tasks->is_task_claimed(G1RP_PS_ClassLoaderDataGraph_oops_do)) { ClassLoaderDataGraph::roots_cld_do(strong_clds, weak_clds); } } { G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::ThreadRoots, worker_i); Threads::possibly_parallel_oops_do(strong_roots, thread_stack_clds, strong_code); } } void ClassLoaderDataGraph::roots_cld_do(CLDClosure* strong, CLDClosure* weak) { for (ClassLoaderData* cld = _head; cld != NULL; cld = cld->_next) { CLDClosure* closure = cld->keep_alive() ? strong : weak; if (closure != NULL) { closure->do_cld(cld); } } } void ClassLoaderData::oops_do(OopClosure* f, KlassClosure* klass_closure, bool must_claim) { if (must_claim && !claim()) { return; } f->do_oop(&_class_loader); _dependencies.oops_do(f); _handles->oops_do(f); if (klass_closure != NULL) { classes_do(klass_closure); } } void ClassLoaderData::classes_do(KlassClosure* klass_closure) { for (Klass* k = _klasses; k != NULL; k = k->next_link()) { klass_closure->do_klass(k); assert(k != k->next_link(), "no loops!"); } }
最终发现调用的
G1KlassScanClosure
中的do_klass
class G1KlassScanClosure : public KlassClosure { G1ParCopyHelper* _closure; bool _process_only_dirty; int _count; public: G1KlassScanClosure(G1ParCopyHelper* closure, bool process_only_dirty) : _process_only_dirty(process_only_dirty), _closure(closure), _count(0) {} void do_klass(Klass* klass) { if (!_process_only_dirty || klass->has_modified_oops()) { klass->clear_modified_oops(); _closure->set_scanned_klass(klass); klass->oops_do(_closure); _closure->set_scanned_klass(NULL); } _count++; } };
主要执行
klass->oops_do(_closure);
,这个f为G1ParCopyHelper的对象,所以最终调用的g1CollectedHeap.cpp@G1ParCopyClosure#do_oop_workG1ParCopyHelper
的do_oop
最终调用do_oop_work
来把活跃对象复制到新分区。针对线程的处理则是在thread.cpp#possibly_parallel_oops_do
Threads::possibly_parallel_oops_do(strong_roots, thread_stack_clds, strong_code);
实际调用JavaThread::oops_do
遍历栈桢void Thread::oops_do(OopClosure* f, CLDClosure* cld_f, CodeBlobClosure* cf) { active_handles()->oops_do(f); // Do oop for ThreadShadow f->do_oop((oop*)&_pending_exception); handle_area()->oops_do(f); } void JavaThread::oops_do(OopClosure* f, CLDClosure* cld_f, CodeBlobClosure* cf) { Thread::oops_do(f, cld_f, cf); assert( (!has_last_Java_frame() && java_call_counter() == 0) || (has_last_Java_frame() && java_call_counter() > 0), "wrong java_sp info!"); if (has_last_Java_frame()) { RememberProcessedThread rpt(this); if (_privileged_stack_top != NULL) { _privileged_stack_top->oops_do(f); } if (_array_for_gc != NULL) { for (int index = 0; index < _array_for_gc->length(); index++) { f->do_oop(_array_for_gc->adr_at(index)); } } for (MonitorChunk* chunk = monitor_chunks(); chunk != NULL; chunk = chunk->next()) { chunk->oops_do(f); } for(StackFrameStream fst(this); !fst.is_done(); fst.next()) { fst.current()->oops_do(f, cld_f, cf, fst.register_map()); } } set_callee_target(NULL); assert(vframe_array_head() == NULL, "deopt in progress at a safepoint!"); GrowableArray* list = deferred_locals(); if (list != NULL) { for (int i = 0; i < list->length(); i++) { list->at(i)->oops_do(f); } } f->do_oop((oop*) &_threadObj); f->do_oop((oop*) &_vm_result); f->do_oop((oop*) &_exception_oop); f->do_oop((oop*) &_pending_async_exception); if (jvmti_thread_state() != NULL) { jvmti_thread_state()->oops_do(f); } }
从JNI本地代码栈和JVM内部方法栈中找活跃对象,从java栈中找,遍历Monitor块,遍历jvmti(JVM Tool Interface)这里主要使用是JavaAgent。最后执行
G1ParCopyHelper
的do_oop
最终调用do_oop_work
来把活跃对象复制到新分区。 -
-
JVM根
一些全局JVM对象,如Universe,JNIHandles,SystemDictionary,StringTable等等
void G1RootProcessor::process_vm_roots(OopClosure* strong_roots, OopClosure* weak_roots, G1GCPhaseTimes* phase_times, uint worker_i) { { G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::UniverseRoots, worker_i); if (!_process_strong_tasks->is_task_claimed(G1RP_PS_Universe_oops_do)) { Universe::oops_do(strong_roots); } } .... void Universe::oops_do(OopClosure* f, bool do_all) { f->do_oop((oop*) &_int_mirror); f->do_oop((oop*) &_float_mirror); f->do_oop((oop*) &_double_mirror); ........ }
针对JVM根 同样也是调用的
G1ParCopyHelper
的do_oop
只不过对JVM根而言则是各种全局对象。例如Univers
g1CollectedHeap.cpp@G1ParCopyClosure#do_oop_work工作流程如下
[图片上传中...(image-ee39c-1675644518439-2)]执行对象复制复制的操作在G1ParScanThreadState#copy_to_survivor_space方法中。具体处理如下
[图片上传中...(image-eb176-1675644518439-1)] -
-
处理RSet
-
我们在G1ParTask的work方法中来看处理RSet的入口。
void G1RootProcessor::scan_remembered_sets(G1ParPushHeapRSClosure* scan_rs, OopClosure* scan_non_heap_weak_roots, uint worker_i) { ... _g1h->g1_rem_set()->oops_into_collection_set_do(scan_rs, &scavenge_cs_nmethods, worker_i); }
主要是去执行G1RemSet中的
oops_into_collection_set_do
方法。主要信息更新RSet和扫描RSet。void G1RemSet::oops_into_collection_set_do(G1ParPushHeapRSClosure* oc, CodeBlobClosure* code_root_cl, uint worker_i) { DirtyCardQueue into_cset_dcq(&_g1->into_cset_dirty_card_queue_set()); updateRS(&into_cset_dcq, worker_i); scanRS(oc, code_root_cl, worker_i); _cset_rs_update_cl[worker_i] = NULL; }
这里看到有个DCQ,在研究RSet的时候就遇到这种队列,当时说的是给予Mutator用于记录应用线程运行时引用情况,这里这个主要是用于记录复制失败后,要保留的引用,此队列数据将传递到用于管理RSet更新的DirtyCardQueueSet。
- 更新RSet
> 主要用于把上面这个DCQ对象存到RSet的PRT当中。 > > * ``` > G1GCParPhaseTimesTracker x(_g1p->phase_times(), G1GCPhaseTimes::UpdateRS, worker_i); > // Apply the given closure to all remaining log entries. > RefineRecordRefsIntoCSCardTableEntryClosure into_cset_update_rs_cl(_g1, into_cset_dcq); > > _g1->iterate_dirty_card_closure(&into_cset_update_rs_cl, into_cset_dcq, false, worker_i); > } > void G1CollectedHeap::iterate_dirty_card_closure(CardTableEntryClosure* cl, > DirtyCardQueue* into_cset_dcq, > bool concurrent, > uint worker_i) { > // Clean cards in the hot card cache > G1HotCardCache* hot_card_cache = _cg1r->hot_card_cache(); > hot_card_cache->drain(worker_i, g1_rem_set(), into_cset_dcq); > > DirtyCardQueueSet& dcqs = JavaThread::dirty_card_queue_set(); > size_t n_completed_buffers = 0; > while (dcqs.apply_closure_to_completed_buffer(cl, worker_i, 0, true)) { > n_completed_buffers++; > } > g1_policy()->phase_times()->record_thread_work_item(G1GCPhaseTimes::UpdateRS, worker_i, n_completed_buffers); > dcqs.clear_n_completed_buffers(); > assert(!dcqs.completed_buffers_exist_dirty(), "Completed buffers exist!"); > } > ``` > > > > 首先使用`RefineRecordRefsIntoCSCardTableEntryClosure`闭包处理,**处理整个卡中如果存在对堆内对象的引用,就是脏卡,就需要入队,被Refine线程处理**。 > > `iterate_dirty_card_closure`方法处理DCQS中剩余的DCQ,和Java线程处理方式一样。
- 扫描Rset
> 根据Rset中的信息找到引用者 > > * ``` > void G1RemSet::scanRS(G1ParPushHeapRSClosure* oc, > CodeBlobClosure* code_root_cl, > uint worker_i) { > double rs_time_start = os::elapsedTime(); > HeapRegion *startRegion = _g1->start_cset_region_for_worker(worker_i); > > ScanRSClosure scanRScl(oc, code_root_cl, worker_i); > > _g1->collection_set_iterate_from(startRegion, &scanRScl); > scanRScl.set_try_claimed(); > _g1->collection_set_iterate_from(startRegion, &scanRScl); > > double scan_rs_time_sec = (os::elapsedTime() - rs_time_start) > - scanRScl.strong_code_root_scan_time_sec(); > > assert(_cards_scanned != NULL, "invariant"); > _cards_scanned[worker_i] = scanRScl.cards_done(); > > _g1p->phase_times()->record_time_secs(G1GCPhaseTimes::ScanRS, worker_i, scan_rs_time_sec); > _g1p->phase_times()->record_time_secs(G1GCPhaseTimes::CodeRoots, worker_i, scanRScl.strong_code_root_scan_time_sec()); > } > ``` > > > > 使用GC线程id分片处理不同的分区,执行流程主要是俩次扫描分区。处理一般对象和代码对象主要处理内联优化之后的代码引用对象。主要执行流程如下 > [图片上传中...(image-63353b-1675644518437-0)]
- 对象复制
-
主要处理根扫描出的对象和 RSet中找到的子对象全部复制到新的分区当中。所有的对象都被放在ParScanState的队列中。执行复制的过程就是从该队列中出队,处理不同的对象类型。最终调用deal_with_reference方法来处理。把cset中所有的活跃对象都复制到新的分区的Survivor或者老年代当中。
网友评论