您现在的位置是:首页 > 文章详情

每日一博 | 深入分析 OpenJDK G1 FullGC 原理

日期:2023-02-20点击:269

导读

本文主要从代码层面介绍 OpenJDK G1(garbage first) FullGC 的工作原理,基于 OpenJDK 从 GC(garbage collection) 入口处开始分析整个 FullGC 原理的核心代码与执行过程;并对比 OpenJDK8 与 OpenJDK11 中单线程与多线程实现 FullGC 的异同。希望帮助大家更深入地理解 G1 FullGC 原理,并了解代码中可能导致 GC 时间长的对象分配方式。

阅读本文需要对 JVM、JDK、java对象、对象内存分配、垃圾回收等概念有一定了解,没有 JVM 基础的同学可参考《深入理解Java虚拟机》,《The Garbage Collection Handbook》等。本文涉及源码可参考openjdk github source code

G1 GC的知识回顾

为了帮助大家更全面地理解 FullGC 的逻辑和前因后果,我们先整体简单介绍下 G1 GC 。GC 主要指在程序运行过程中,对象不再使用后,由 JVM 提供的程序将对象销毁并回收内存,将该内存再加入到可用内存中;而 G1GC,是 JVM 提供的一种垃圾回收器,可以通过多 worker 并行回收,并且通过 CMS 并发标记,此外通过 region 的分代回收,可以较好的控制单次回收的暂停时间,并且有效的抑制碎片化的问题。但 G1 并不是完美的回收器,相比于 CMS GC,G1GC CPU 负载相对较高;吞吐量在小内存上可能相对较弱,但 G1GC 整体在回收时间和效率上都表现较为优秀,是当前 java 业务中使用较为频繁的垃圾回收器。但随着使用场景的变换,为了在业务中更好地完成垃圾回收调优,可以更多地了解下这项技术的基础知识。

  1. G1 将整个 JVM 运行时堆分为若干个 region,region 大小在运行之初设定,且一经设定不可改变;每个 region 都可能被作以下分别:包括 Eden,Survivor,Old,Humongous 和 Free 等区域,其中 Eden region 指年轻代,一般对象(大小小于 region 的一半)对象分配的默认选择区域,并且为每次内存空间不足时的优先回收区;从一次垃圾回收存活下来的对象会被 copy 到 survivor region,经过几轮回收后,依然存活的对象太多,则这些对象会被移动到 Old region;当对象大小大于region 一半会被直接分配到 Humongous region,只有在 Full GC 或者 CMS 后才有机会回收。

  2. 回收包含 4 部分 YoungGC(Pause)、MixGC、Concurrent Marking 和 FullGC

    YoungGC:选择全部年轻代(Eden),根据暂停时间要求决定回收年轻代的数量。

    • stop the world(stw)暂停所有应用线程,G1 创建回收集 Collection Set(cset)
    • 扫描 root,从栈、classloader、JNIHandle 等出发,扫描标记直接引用的对象
    • 基于要回收的 region 的 rset 来扫描标记对应 region 的存活对象
    • 然后将活对象 copy 到新 region
    • 处理其他引用

    Concurrent Marking Cycle: JVM 内存使用比例超过一定阈值,会触发全局并发标记,包括 clean up 阶段(这里会清理出部分大对象)

    MixGC:选择全部年轻代和部分老年代,会按照垃圾占比高低来排序,优先回收垃圾多的 set,G1也得名于此(Garbage first),回收过程同 YoungGC 一样。

    FullGC: MixGC 来不及回收或回收不掉,分配内存仍失败,就会触发全局回收,包括所有年轻代、老年代,大对象以及少量其他的 JVM 管理的内存,过程总体同 YoungGC 一样。不过扫描 root 过程会将整个堆完全扫描完。对于有些高流量的业务程序而言,适当的 FullGC 可以更好的调整整个内存,但一般情况而言,因为回收暂停时间较长,我们应当尽量避免 FullGC。

  3. G1 FullGC 回收流程图简图如下

G1 FullGC入口(触发条件)

触发 FullGC 的条件主要分两大类,第一类是在对象分配时发现内存不足导致分配失败,会触发回收逻辑,在 young gc/mix gc 尝试回收后,都不能释放足够内存时,便会触发 FullGC;第二类为业务代码中主动调用回收内存函数,比如 System.gc。两类最终都会调用到 do_collection(),此处不涉及 parallel,OpenJDK8 和 OpenJDK11 入口相同

 // 各种分配失败时的调用入口 G1CollectedHeap::mem_allocate VM_G1CollectForAllocation::doit() G1CollectedHeap::satisfy_failed_allocation() G1CollectedHeap::do_collection() // System.gc()和 metadata回收 G1CollectedHeap::collect(GCCause::Cause cause) VM_G1CollectFull::doit() G1CollectedHeap::do_full_collection() G1CollectedHeap::do_collection() 

G1 FullGC回收前准备——prepare_collection

从 do_collection 调入后,对于 OpenJDK8,以 G1MarkSweep::invoke_at_safepoint 函数为边界,该函数前为回收准备,之后为收尾工作;而 OpenJDK11 则将整个 prepare_collection、collect 和 complete 三步都用 g1FullCollector 类进行封装,逻辑更加清晰。

准备过程两者相似,可简单分为三类

  • 数据信息检查及记录:包括计时器,trace、safepoint 检查、日志打印等数据处理部分
  • concurrent_mark 相关操作和暂停:包括 concurrent_mark 的 abort,cm discovery 禁用等
  • 回收准备:heap 集合整理清除、policy 策略更新、开启 stw discovery、处理对象偏向锁等
 // openjdk11 void G1FullCollector::prepare_collection() {   _heap->g1_policy()->record_full_collection_start();   _heap->print_heap_before_gc();   _heap->print_heap_regions(); // 终止并发标记,禁用cm discovery   _heap->abort_concurrent_cycle();   _heap->verify_before_full_collection(scope()->is_explicit_gc()); // 预处理当前堆   _heap->gc_prologue(true);   _heap->prepare_heap_for_full_collection(); ... // 保存偏向锁的markword并进行初始标记   BiasedLocking::preserve_marks();   clear_and_activate_derived_pointers(); } void BiasedLocking::preserve_marks() { ...   Thread* cur = Thread::current();   for (JavaThread* thread = Threads::first(); thread != NULL; thread = thread->next()) {     if (thread->has_last_Java_frame()) {       RegisterMap rm(thread);       for (javaVFrame* vf = thread->last_java_vframe(&rm); vf != NULL; vf = vf->java_sender()) {         GrowableArray<MonitorInfo*> *monitors = vf->monitors();         if (monitors != NULL) {           int len = monitors->length();           // Walk monitors youngest to oldest           for (int i = len - 1; i >= 0; i--) {             MonitorInfo* mon_info = monitors->at(i);             if (mon_info->owner_is_scalar_replaced()) continue;             oop owner = mon_info->owner();             if (owner != NULL) {               markOop mark = owner->mark();               if (mark->has_bias_pattern()) {                 _preserved_oop_stack->push(Handle(cur, owner));                 _preserved_mark_stack->push(mark);               }             }           }         }       }     }   } } 

上述代码为垃圾回收前准备过程的封装,OpenJDK8和OpenJDK11整体过程基本一致(可进入调用函数与 OpenJDK8 对照,此处不展开),区别在于 OpenJDK11 会在 g1FullCollector 初始化过程中,根据 parallel 的使用情况对 preserve_stack、oop_stack、arrayOop_stack、compaction_points 等数据结构按照并行的线程数进行切分,初始化并用数组保存,为 parallel 回收做准备,服务于并行 FullGC。

 // openjdk11 G1FullCollector::G1FullCollector(G1CollectedHeap* heap, GCMemoryManager* memory_manager, bool explicit_gc, bool clear_soft_refs) :     _heap(heap),     _scope(memory_manager, explicit_gc, clear_soft_refs),     _num_workers(calc_active_workers()),     _oop_queue_set(_num_workers),     _array_queue_set(_num_workers),     _preserved_marks_set(true),     _serial_compaction_point(),     _is_alive(heap->concurrent_mark()->nextMarkBitMap()),     _is_alive_mutator(heap->ref_processor_stw(), &_is_alive),     _always_subject_to_discovery() {    assert(SafepointSynchronize::is_at_safepoint(), "must be at a safepoint");   _preserved_marks_set.init(_num_workers);   _markers = NEW_C_HEAP_ARRAY(G1FullGCMarker*, _num_workers, mtGC);   _compaction_points = NEW_C_HEAP_ARRAY(G1FullGCCompactionPoint*, _num_workers, mtGC);   for (uint i = 0; i < _num_workers; i++) {     _markers[i] = new G1FullGCMarker(i, _preserved_marks_set.get(i), mark_bitmap());     _compaction_points[i] = new G1FullGCCompactionPoint();     _oop_queue_set.register_queue(i, marker(i)->oop_stack());     _array_queue_set.register_queue(i, marker(i)->objarray_stack());   } } 

G1 FullGC回收核心逻辑——collect

回收核心过程基本分为4步如下,包括标记阶段(phase1_mark_live_objects),复制和回收准备阶段(phase2_prepare_compaction),调整指针阶段(phase3_adjust_pointers)和压缩清理阶段(phase4_do_compaction):

 // openjdk11 void G1FullCollector::collect() {   phase1_mark_live_objects();   phase2_prepare_compaction();   phase3_adjust_pointers();   phase4_do_compaction(); } 

OpenJDK11 中,这4步骤被封装成 4个 task ,继承自 AbstractGangTask,都保存有 monitor 和可执行的 GangWorker,利用 run_task 来根据 worker 数目分多线程执行 task,而执行逻辑封装在 task 的 work 函数中,每个 task 中会传入 worker_id,操作时根据 worker_id 来利用 G1FullCollector 的数据结构来进行操作。

void G1FullCollector::phase1_mark_live_objects() {   GCTraceTime tm("Phase 1: Mark live objects", G1Log::fine(), true, scope()->timer(), scope()->tracer()->gc_id());   // Do the actual marking.   G1FullGCMarkTask marking_task(this);   run_task(&marking_task); ... } void G1FullCollector::run_task(AbstractGangTask* task) { ...   _heap->workers()->run_task(task); ... } 

1. phase1_mark_live_objects

以 marker 过程为例,第 5 行根据 worker_id 取到 collector 中准备好的数据结构,通过root_processor 来来执行三个闭包函数,最后会一路调用到每个 OOP 的 mark_and_push,对整个堆进行标记,此过程与 OpenJDK8 基本一致。

 // openjdk11 void G1FullGCMarkTask::work(uint worker_id) {   Ticks start = Ticks::now();   ResourceMark rm;   G1FullGCMarker* marker = collector()->marker(worker_id);   MarkingCodeBlobClosure code_closure(marker->mark_closure(), !CodeBlobToOopClosure::FixRelocations);   if (ClassUnloading) {     _root_processor.process_strong_roots(         marker->mark_closure(),         marker->cld_closure(),         &code_closure);   } else {     _root_processor.process_all_roots_no_string_table(         marker->mark_closure(),         marker->cld_closure(),         &code_closure);   }   // Mark stack is populated, now process and drain it.   marker->complete_marking(collector()->oop_queue_set(), collector()->array_queue_set(), &_terminator);   // This is the point where the entire marking should have completed.   assert(marker->oop_stack()->is_empty(), "Marking should have completed");   assert(marker->objarray_stack()->is_empty(), "Array marking should have completed"); } void G1RootProcessor::process_strong_roots(OopClosure* oops, CLDClosure* clds, CodeBlobClosure* blobs) { process_java_roots(oops, clds, clds, NULL, blobs, NULL, 0); process_vm_roots(oops, NULL, NULL, 0); _process_strong_tasks.all_tasks_completed(); } 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); } } // 各类可能的roots ... } bool SubTasksDone::is_task_claimed(uint t) { assert(0 <= t && t < _n_tasks, "bad task id."); uint old = _tasks[t]; if (old == 0) { old = Atomic::cmpxchg(1, &_tasks[t], 0); } bool res = old != 0; ... return res; } template <class T> inline void G1FullGCMarker::mark_and_push(T* p) {   T heap_oop = oopDesc::load_heap_oop(p);   if (!oopDesc::is_null(heap_oop)) {     oop obj = oopDesc::decode_heap_oop_not_null(heap_oop);     if (mark_object(obj)) {       _oop_stack.push(obj);       assert(_bitmap->isMarked((HeapWord*) obj), "Must be marked now - map self");     } else {       assert(_bitmap->isMarked((HeapWord*) obj), "Must be marked by other");     }   } } 

补充多线程实现思路

以 mark 过程为例,简单说明下多线程回收思路。

1)首先在 java 程序执行时,可以通过添加 -XX:ParallelGCThreads=8 指定具体回收时的线程数;在启动后,SharedHeap 对象的初始化时,会调用 WorkGang::initialize_workers(),初始化 8 个 GC 线程(一般是以 GangWorker 实现,继承自 WorkerThread,也被称为工人线程)。随后线程开始执行会调用到 GangWorker 的 run 函数,调到 loop,进入 wait 阶段等待唤醒来执行任务,这个循环等待会持续整个 java 程序运行过程。

void GangWorker::run() {   initialize();   loop(); } void GangWorker::loop() { ...   Monitor* gang_monitor = gang()->monitor();   for ( ; /* !terminate() */; ) {     WorkData data;     int part;  // Initialized below.     {       MutexLocker ml(gang_monitor);       gang()->internal_worker_poll(&data);      for ( ; /* break or return */; ) {         // Check for new work.         if ((data.task() != NULL) &&             (data.sequence_number() != previous_sequence_number)) {           if (gang()->needs_more_workers()) {             gang()->internal_note_start();             gang_monitor->notify_all();             part = gang()->started_workers() - 1;             break;           }         }         // Nothing to do.         gang_monitor->wait(/* no_safepoint_check */ true);         gang()->internal_worker_poll(&data); ...     data.task()->work(part); ...       } } } } 

2)所有的 XXXTask 继承自 AbstractGangTask,会实现 work 方法;在回收过程执行时,会将待执行的 task 传给 heap 的 workers 来执行,即会调用到 Sharedheap 中提前保存好的 FlexibleWorkGang 的 run_task。而 FlexibleWorkGang 继承自 WorkGang,run_task 方法实现如下,会唤醒所有GC线程,并等待所有线程执行结束后返回。

// workgroup.cpp void FlexibleWorkGang::run_task(AbstractGangTask* task) {   WorkGang::run_task(task, (uint) active_workers()); } void WorkGang::run_task(AbstractGangTask* task, uint no_of_parallel_workers) {   task->set_for_termination(no_of_parallel_workers);   MutexLockerEx ml(monitor(), Mutex::_no_safepoint_check_flag); ...   monitor ()-> notify_all ();   // Wait for them to be finished   while (finished_workers() < no_of_parallel_workers) {     if (TraceWorkGang) {       tty->print_cr("Waiting in work gang %s: %d/%d finished sequence %d",                     name(), finished_workers(), no_of_parallel_workers,                     _sequence_number);     }     monitor()->wait(/* no_safepoint_check */ true);   } ... } 

3)task 开始执行后,会通过 processor 执行器执行到每个 task 的 work 方法,并根据执行线程传入worker_id,比如 G1FullGCMarkTask 的 _root_processor.process_strong_roots 函数,调用关系的其中一条分支如下,每一个 workgang 都会通过 ALL_JAVA_THREADS 来顺序遍历所有的 java 线程来执行 mark 操作,若已经执行过或正在执行就跳过,保证执行完所有线程,包括一个 VMThread,其中 mark 过程传入的 OopClosure、CLDClosure 为对应 worker_id 的 mark_closure、cld_closure,执行时会调用每个 marker 准备好的 oopStack 等数据结构来执行,都执行结束后所有的 GangWorker继续进入等待任务的过程中,直到下一个 run_task。

// process_strong_roots // -> process_java_roots // --> Threads::possibly_parallel_oops_do void Threads::possibly_parallel_oops_do(OopClosure* f, CLDClosure* cld_f, CodeBlobClosure* cf) {   SharedHeap* sh = SharedHeap::heap();   bool is_par = sh->n_par_threads() > 0;   assert(!is_par ||          (SharedHeap::heap()->n_par_threads() ==           SharedHeap::heap()->workers()->active_workers()), "Mismatch");   int cp = SharedHeap::heap()->strong_roots_parity();   ALL_JAVA_THREADS(p) {     if (p->claim_oops_do(is_par, cp)) {       p->oops_do(f, cld_f, cf);     }   }   VMThread* vmt = VMThread::vm_thread();   if (vmt->claim_oops_do(is_par, cp)) {     vmt->oops_do(f, cld_f, cf);   } } 

2. phase2_prepare_compaction

标记过后,准备压缩清理,根据标记情况计算地址,详细逻辑在 G1CalculatePointersClosure 实现如下;根据标记结果,通过 doHeapRegion 遍历含有存活对象的 region,并将存活对象复制到新的 region 内,copy 同时将对象头指向旧对象,为调整指针和清理旧 region 做准备。

 // openjdk11 void G1FullGCPrepareTask::work(uint worker_id) {   ...   G1FullGCCompactionPoint* compaction_point = collector()->compaction_point(worker_id);   G1CalculatePointersClosure closure(collector()->mark_bitmap(), compaction_point);   G1CollectedHeap::heap()->heap_region_par_iterate_from_start(&closure, &_hrclaimer); ... } void G1CollectedHeap::heap_region_par_iterate_from_start(HeapRegionClosure* cl, HeapRegionClaimer *hrclaimer) const { _hrm.par_iterate(cl, hrclaimer, 0); } void HeapRegionManager::par_iterate(HeapRegionClosure* blk, HeapRegionClaimer* hrclaimer, const uint start_index) const { const uint n_regions = hrclaimer->n_regions(); for (uint count = 0; count < n_regions; count++) { const uint index = (start_index + count) % n_regions; ... HeapRegion* r = _regions.get_by_index(index); if (hrclaimer->is_region_claimed(index)) { continue; } if (!hrclaimer->claim_region(index)) { continue; } bool res = blk->do_heap_region(r); if (res) { return; } } } bool G1FullGCPrepareTask::G1CalculatePointersClosure::doHeapRegion(HeapRegion* hr) {   if (hr->isHumongous()) {     oop obj = oop(hr->humongous_start_region()->bottom());     if (_bitmap->isMarked((HeapWord*) obj)) {       if (hr->startsHumongous()) {         obj->forward_to(obj);       }     } else {       free_humongous_region(hr);     }   } else if (!hr->isHumongous()) {     prepare_for_compaction(hr);   }   reset_region_metadata(hr);   return false; } 

3. phase3_adjust_pointers

此步骤和phase2类似,遍历存活对象region,通过对象头记录的地址,将 live 的对象引用指向重新计算的地址。由 phase2 和 phase3 两个步骤可见,同样大小的内存占用,在 GC 过程中,存活对象数量越多会引起更多的复制和调整指针工作,导致更长的总回收时间。

 // openjdk11 void G1FullGCAdjustTask::work(uint worker_id) { ...   // Needs to be last, process_all_roots calls all_tasks_completed(...).   _root_processor.process_all_roots(       &_adjust,       &adjust_cld,       &adjust_code); ...   // 在此步骤前adjust掉weak,preserved string等等, // 此步骤adjust pointers region by region   G1AdjustRegionClosure blk(collector()->mark_bitmap(), worker_id);   G1CollectedHeap::heap()->heap_region_par_iterate_from_worker_offset(&blk, &_hrclaimer, worker_id);   log_task("Adjust task", worker_id, start); } 

4. phase4_do_compaction

最后逐个 region 进行处理,若没有存活对象,清理掉 region;若有存活对象,则将对象按之前计算copy 到新地址,将旧 region 重置。

 // openjdk11 void G1FullGCCompactTask::work(uint worker_id) {   Ticks start = Ticks::now();   GrowableArray<HeapRegion*>* compaction_queue = collector()->compaction_point(worker_id)->regions();   for (GrowableArrayIterator<HeapRegion*> it = compaction_queue->begin();        it != compaction_queue->end();        ++it) {     compact_region(*it);   }   G1ResetHumongousClosure hc(collector()->mark_bitmap());   G1CollectedHeap::heap()->heap_region_par_iterate_from_worker_offset(&hc, &_claimer, worker_id); ... } void G1FullGCCompactTask::compact_region(HeapRegion* hr) {   ...   G1CompactRegionClosure compact(collector()->mark_bitmap());   hr->apply_to_marked_objects(collector()->mark_bitmap(), &compact);  if (!hr->is_empty()) {    MemRegion mr(hr->bottom(), hr->top());    collector()->mark_bitmap()->clearRange(mr);   }   hr->complete_compaction(); } size_t G1FullGCCompactTask::G1CompactRegionClosure::apply(oop obj) {   size_t size = obj->size();   HeapWord* destination = (HeapWord*)obj->forwardee();   if (destination == NULL) {     return size;   }   HeapWord* obj_addr = (HeapWord*) obj;   assert(obj_addr != destination, "everything in this pass should be moving");   Copy::aligned_conjoint_words(obj_addr, destination, size);   oop(destination)->init_mark();   assert(oop(destination)->klass() != NULL, "should have a class");   return size; } inline void HeapRegion::complete_compaction() { ...   if (used_region().is_empty()) {     reset_bot();   } ...   if (ZapUnusedHeapArea) {     SpaceMangler::mangle_region(MemRegion(top(), end()));   } } 

G1 FullGC回收收尾——complete

在整个回收结束前,需要进行一些收尾工作,此处并无 parallel 区分。在 prepare_heap_for_mutators中,会包括重建 region 集合,将空的 region 加入 free_list;为每个 region 重建 strong root 列表;删除已卸载类加载器的元空间,并清理 loader_data 图;准备 collection set;重新处理卡表信息以及一些验证和信息打印等。

 // openjdk11 void G1FullCollector::complete_collection() { 。。。   restore_marks();   BiasedLocking::restore_marks();   CodeCache::gc_epilogue();   JvmtiExport::gc_epilogue();   _heap->prepare_heap_for_mutators(); ... } 

总结

相比于 YoungGC 和 Concurrent Marking Cycle 等,G1 回收器中 FullGC 过程更为独立、完整,但涉及面也更广,涉及到 JVM 对整个堆的管理细节,包括 OOP 对象结构,region 块,bitmap 标记管理,起始 root 管理,甚至GC线程后台运行情况,多线程同步,stw机制等等,每一部分的设计都很巧妙,值得我们深入代码中一探究竟,相信大家一定会有所收获。

原文链接:https://my.oschina.net/u/6150560/blog/7928455
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章