[转帖]深入分析 OpenJDK G1 FullGC 原理

深入分析,openjdk,g1,fullgc,原理 · 浏览次数 : 0

小编点评

**G1 FullGC 过程概述** G1 FullGC 过程是 Java GC 的核心回收器,负责回收内存中的对象,并将它们重新分配到新区域中。这个过程包含多个阶段,每个阶段都负责回收特定类型的对象。 **G1 FullGC 过程阶段** **1. 准备阶段** * 重置所有类加载器元空间。 * 清理 loader_data 图。 **2. 区域回收阶段** * 遍历所有可回收的 heap region,并重建 region 集合。 * 将空 region 加入 free_list。 **3. bitmap标记回收阶段** * 遍历所有 bitmap,并回收它们。 * 将已回收的 bitmap 清理掉。 **4. 对象结构回收阶段** * 遍历所有对象结构,并回收它们。 * 将已回收的对象结构清理掉。 **5. 区域回收阶段** * 遍历所有可回收的 heap region,并回收它们。 * 将空 region 清理掉。 **6. 结束阶段** * 清理所有垃圾回收器。 * 重置所有类加载器元空间。 **G1 FullGC 的关键设计** * G1 回收器是一个独立的线程,负责回收所有对象。 * G1 回收器使用并发策略,以提高回收效率。 * G1 回收器包含许多优化技术,以减少内存回收时间。 **G1 FullGC 的优缺点** **优点** * G1 回收器是一个独立的线程,以提高回收效率。 * G1 回收器使用并发策略,以提高回收效率。 * G1 回收器包含许多优化技术,以减少内存回收时间。 **缺点** * G1 回收器是一个独立的线程,以提高回收效率。 * G1 回收器使用并发策略,以提高回收效率。 * G1 回收器可能在回收对象时阻塞,导致性能下降。

正文

https://my.oschina.net/u/6150560/blog/7928455

 

 

欢迎关注【字节跳动 SYS Tech】公众号。字节跳动 SYS Tech 聚焦系统技术领域,与大家分享前沿技术动态、技术创新与实践、行业技术热点分析等内容。

导读

本文主要从代码层面介绍 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 机制等等,每一部分的设计都很巧妙,值得我们深入代码中一探究竟,相信大家一定会有所收获。

与[转帖]深入分析 OpenJDK G1 FullGC 原理相似的内容:

[转帖]深入分析 OpenJDK G1 FullGC 原理

https://my.oschina.net/u/6150560/blog/7928455 欢迎关注【字节跳动 SYS Tech】公众号。字节跳动 SYS Tech 聚焦系统技术领域,与大家分享前沿技术动态、技术创新与实践、行业技术热点分析等内容。 导读 本文主要从代码层面介绍 OpenJDK G1

[转帖]线上大量CLOSE_WAIT的原因深入分析

这一次重启真的无法解决问题了:一次 MySQL 主动关闭,导致服务出现大量 CLOSE_WAIT 的全流程排查过程。 近日遇到一个线上服务 socket 资源被不断打满的情况。通过各种工具分析线上问题,定位到问题代码。这里对该问题发现、修复过程进行一下复盘总结。 先看两张图。一张图是服务正常时监控到

[转帖]Tomcat 优雅关闭之路

本文首发于 vivo互联网技术 微信公众号链接:https://mp.weixin.qq.com/s/ZqkmoAR4JEYr0x0Suoq7QQ作者:马运杰 本文通过阅读Tomcat启动和关闭流程的源码,深入分析不同的Tomcat关闭方式背后的原理,让开发人员能够了解在使用不同的关闭方式时需要注意

[转帖]HikariCP常用监控指标与故障排查实战

编者有言:本书由资深数据库连接池专家撰写,褚霸、德哥、张亮、吴晟等近10位专家高度评价,从设计思想、功能使用、原理实现、工程实践、技术扩展5个维度对HikariCP进行全面讲解和深入分析。 本文将带你抢先阅读 第10章及第14章节选内容 作者介绍 朱政科,资深架构师,十年IT老兵,对各种数据库连接池

[转帖]深入理解同步机制---内核自旋锁

https://switch-router.gitee.io/blog/spinlock/ 进程(线程)间的同步机制是面试时的常见问题,所以准备用一个系列来好好整理下用户态与内核态的各种同步机制。本文就以内核空间的一种基础同步机制—自旋锁开始好了 自旋锁是什么 自旋锁就是一个二状态的原子(atomi

[转帖]深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接

https://www.cnblogs.com/jiangjunli/p/10617034.html 1、内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符)。包括相等联接和自然联接。 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。例如,检索 students和course

[转帖]深入理解Redis的持久化

https://www.cnblogs.com/ivictor/p/9749465.html RDB RDB是将当前数据生成快照保存到硬盘上。 RDB的工作流程: 1. 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。

[转帖]深入学习MySQL事务:ACID特性的实现原理

https://www.cnblogs.com/kismetv/p/10331633.html 事务是MySQL等关系型数据库区别于NoSQL的重要方面,是保证数据一致性的重要手段。本文将首先介绍MySQL事务相关的基础概念,然后介绍事务的ACID特性,并分析其实现原理。 MySQL博大精深,文章疏

[转帖]深入内存/主存:解剖DRAM存储器

https://zhuanlan.zhihu.com/p/561501585 2022/9/9更新:经过和评论区大佬的交流,准备研读一下JEDEC标准,主要是加深自己对banking和访存加速的理解(对应本文的第五节,主要问题集中在提出banking是为了隐藏row缺失造成的损失还是为了隐藏CPU和

[转帖]深入理解 netfilter 和 iptables

Netfilter (配合 iptables)使得用户空间应用程序可以注册内核网络栈在处理数据包时应用的处理规则,实现高效的网络转发和过滤。很多常见的主机防火墙程序以及 Kubernetes 的 Service 转发都是通过 iptables 来实现的。 关于 netfilter 的介绍文章大部分只