Fundamentals 18 min read

Unlocking G1 FullGC: Inside OpenJDK’s Garbage‑First Full Collection Mechanism

This article dives deep into the OpenJDK G1 FullGC implementation, explaining its architecture, trigger conditions, preparation steps, and the four‑phase collection process while comparing single‑threaded and multi‑threaded implementations in JDK 8 and JDK 11, and highlighting code paths that can cause long GC pauses.

ByteDance SYS Tech
ByteDance SYS Tech
ByteDance SYS Tech
Unlocking G1 FullGC: Inside OpenJDK’s Garbage‑First Full Collection Mechanism

G1 FullGC Overview

The article explains the working principle of OpenJDK G1 (Garbage‑First) FullGC from the code level, starting at the GC entry point and analyzing core source code and execution flow. It compares single‑threaded and multi‑threaded implementations in OpenJDK 8 and OpenJDK 11, helping readers understand how object allocation patterns can affect GC duration.

G1 GC Basics

G1 divides the Java heap into fixed‑size regions (Eden, Survivor, Old, Humongous, Free). YoungGC collects only young regions, MixGC collects young plus selected old regions, and FullGC collects the entire heap when allocation fails or System.gc() is invoked. G1 offers parallel workers, concurrent marking, and region‑based generational collection, reducing pause times compared to CMS but with higher CPU load.

FullGC Trigger Conditions

FullGC is triggered when allocation fails after YoungGC/MixGC attempts, or when the application explicitly calls System.gc(). Both paths converge on do_collection() (no parallel variant) in JDK 8 and JDK 11.

<code>// Allocation failure entry
G1CollectedHeap::mem_allocate
    VM_G1CollectForAllocation::doit()
        G1CollectedHeap::satisfy_failed_allocation()
            G1CollectedHeap::do_collection()

// System.gc() entry
G1CollectedHeap::collect(GCCause::Cause cause)
    VM_G1CollectFull::doit()
        G1CollectedHeap::do_full_collection()
            G1CollectedHeap::do_collection()</code>

Preparation Phase (prepare_collection)

In JDK 8 the preparation ends at G1MarkSweep::invoke_at_safepoint . JDK 11 encapsulates preparation, collection, and completion in the G1FullCollector class, making the logic clearer. Preparation includes data checks, concurrent‑mark handling, and heap‑wide cleanup.

<code>// JDK 11 preparation example
void G1FullCollector::prepare_collection() {
  _heap->g1_policy()->record_full_collection_start();
  _heap->print_heap_before_gc();
  _heap->print_heap_regions();
  _heap->abort_concurrent_cycle();
  _heap->verify_before_full_collection(scope()->is_explicit_gc());
  _heap->gc_prologue(true);
  _heap->prepare_heap_for_full_collection();
  // ... other init ...
}</code>

Core Collection Logic (collect)

The FullGC process consists of four phases:

Phase 1 – Mark live objects

Phase 2 – Prepare compaction (calculate new addresses)

Phase 3 – Adjust pointers

Phase 4 – Perform compaction

<code>void G1FullCollector::collect() {
  phase1_mark_live_objects();
  phase2_prepare_compaction();
  phase3_adjust_pointers();
  phase4_do_compaction();
}</code>

Each phase is implemented as a task derived from AbstractGangTask and executed by a pool of GangWorker threads. Workers are created based on -XX:ParallelGCThreads and run in a loop, waiting for tasks and processing them in parallel.

<code>// Worker loop
void GangWorker::run() {
  initialize();
  loop();
}

void GangWorker::loop() {
  for (;;) {
    WorkData data;
    {
      MutexLocker ml(gang()->monitor());
      gang()->internal_worker_poll(&data);
    }
    if (data.task() != NULL) {
      data.task()->work(part);
    }
    gang()->monitor()->wait(true);
  }
}</code>

Phase Details

Phase 1 – Mark Live Objects

Workers create a G1FullGCMarkTask that processes strong roots, marks live objects, and fills marking stacks. After marking, each worker calls marker->complete_marking() to finish.

<code>void G1FullGCMarkTask::work(uint worker_id) {
  G1FullGCMarker* marker = collector()->marker(worker_id);
  _root_processor.process_strong_roots(marker->mark_closure(),
                                      marker->cld_closure(),
                                      &code_closure);
  marker->complete_marking(collector()->oop_queue_set(),
                           collector()->array_queue_set(),
                           &_terminator);
}</code>

Phase 2 – Prepare Compaction

After marking, G1FullGCPrepareTask computes new object addresses using G1CalculatePointersClosure and iterates over live regions.

<code>void G1FullGCPrepareTask::work(uint worker_id) {
  G1FullGCCompactionPoint* cp = collector()->compaction_point(worker_id);
  G1CalculatePointersClosure closure(collector()->mark_bitmap(), cp);
  G1CollectedHeap::heap()->heap_region_par_iterate_from_start(&closure, &_hrclaimer);
}</code>

Phase 3 – Adjust Pointers

Workers run G1FullGCAdjustTask which processes all roots again, this time updating references to the new locations.

<code>void G1FullGCAdjustTask::work(uint worker_id) {
  _root_processor.process_all_roots(&adjust, &adjust_cld, &adjust_code);
  G1AdjustRegionClosure blk(collector()->mark_bitmap(), worker_id);
  G1CollectedHeap::heap()->heap_region_par_iterate_from_worker_offset(&blk, &_hrclaimer, worker_id);
}</code>

Phase 4 – Compaction

Finally, G1FullGCCompactTask copies live objects to their new regions and clears old regions.

<code>void G1FullGCCompactTask::work(uint worker_id) {
  for (auto* hr : *collector()->compaction_point(worker_id)->regions()) {
    compact_region(hr);
  }
  // reset humongous regions and iterate remaining regions
}</code>

Completion

After all phases, G1FullCollector::complete_collection() restores marks, updates biased locking, runs GC epilogues, and prepares the heap for mutators, rebuilding region lists and cleaning up metadata.

<code>void G1FullCollector::complete_collection() {
  restore_marks();
  BiasedLocking::restore_marks();
  CodeCache::gc_epilogue();
  JvmtiExport::gc_epilogue();
  _heap->prepare_heap_for_mutators();
}</code>

Conclusion

Compared with YoungGC and Concurrent Marking, G1 FullGC is a more comprehensive, independent process that touches many JVM subsystems, including object layout, region management, bitmap marking, root handling, and multithreaded coordination, offering valuable insights for performance tuning.

JavaGarbage CollectionmultithreadingOpenJDKFullGCG1 GC
ByteDance SYS Tech
Written by

ByteDance SYS Tech

Focused on system technology, sharing cutting‑edge developments, innovation and practice, and analysis of industry tech hotspots.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.