rgoussu@goussu: ~/library/java/memory-and-gc
~/library/java/memory-and-gc cat garbage-collectors-compared.md

Garbage collectors compared — G1, ZGC, Shenandoah & the rest

# HotSpot's collectors — Serial, Parallel, G1, ZGC, Shenandoah, Epsilon — compared on algorithm, pause profile and throughput, with guidance on choosing and first-step tuning.

Conceptsaved 2026-08-09 #java#jvm#gc#performance#tuning

Overview

HotSpot ships six collectors because no single point on the throughput/latency/footprint triangle suits every workload. They are all assemblies of the same algorithmic parts — what differs is which pauses they accept and where they spend concurrent CPU. Since Java 21 the practical shortlist is short: G1 unless proven otherwise, ZGC (generational) when tail latency rules, Parallel for throughput-only batch — the rest are niches.

Key points

  • Serial (-XX:+UseSerialGC) — single-threaded copying young + mark-compact old, fully stop-the-world. Right answer only for tiny heaps and single-CPU containers; chosen automatically on very small machines.
  • Parallel (-XX:+UseParallelGC) — same design, all cores. Highest throughput, zero concurrent overhead, but full-heap pauses grow with heap size. The batch-job collector; was the default until Java 8.
  • G1 (-XX:+UseG1GC, default since 9) — regional, generational; concurrent old-gen marking (SATB), stop-the-world incremental evacuation of the most-garbage-first regions, paced against -XX:MaxGCPauseMillis (default 200 ms). The balanced default — predictable enough pauses at modest throughput cost.
  • ZGC (-XX:+UseZGC, generational by default since 23) — everything concurrent (marking and relocation) via colored pointers + load barriers. Sub-millisecond pauses independent of heap size, up to multi-TB heaps; costs single-digit % throughput and some memory headroom. Needs 64-bit; no compressed oops.
  • Shenandoah (-XX:+UseShenandoahGC) — same goal as ZGC, different mechanics (Brooks-forwarding lineage, SATB); generational mode arrived in 24. Historically the Red Hat-backed option, notably available on builds where ZGC wasn't.
  • Epsilon (-XX:+UseEpsilonGC) — allocates, never collects, dies on heap exhaustion. For allocation benchmarking and short-lived, pre-sized processes.
  • Gone / elsewhere: CMS was removed in Java 14 (G1 is its successor); OpenJ9 has its own policy roster (gencon, balanced, metronome) and Azul C4 is the commercial fully-concurrent compactor — see JVM implementations.
  • Tune in this order: (1) turn on -Xlog:gc* and measure; (2) size the heap (-Xms=-Xmx to kill resize churn); (3) pick the collector for the workload; (4) set the collector's goal knob (G1 pause target); (5) only then touch fine-grained flags — and never copy a flag set from a blog older than your JDK.

Details

The comparison

Collector Design Young pause Old/full pause Throughput Sweet spot
Serial STW copying + mark-compact ms–100s ms seconds low <~1 GB heaps, 1 CPU
Parallel STW, all cores short seconds on big heaps highest batch, ETL — pauses free
G1 regional, concurrent mark + STW incremental evacuation ~10s of ms, target-paced avoided (mixed collections); fallback full GC = trouble high default; most services
ZGC (gen.) fully concurrent, load barriers, colored pointers <1 ms <1 ms high minus ~5% latency SLOs, huge heaps
Shenandoah fully concurrent, SATB ~1 ms ~1 ms similar to ZGC same niche, RH ecosystems
Epsilon none — (OOM instead) maximal until OOM benchmarks, one-shot jobs

Choosing, honestly

  • Pauses invisible to your users (batch, queues with slack)? → Parallel, enjoy the free throughput.
  • p99/p999 latency in the SLO, or heap >32 GB? → generational ZGC; verify the throughput cost is within budget under real load.
  • Everything else — start with G1 and read GC logs before believing you've outgrown it. Frequent full GCs under G1 are a sizing/promotion problem to diagnose (with jstat, jmap, JFR), not a reason to switch collector.
  • Wildly different pause/footprint constraints (containers, 100 MB heaps)? Consider whether the runtime is the variable: OpenJ9 or native image — see JVM implementations.

Related

Citations

[1] HotSpot GC Tuning Guide (Java 21) [2] JEP 439: Generational ZGC [3] JEP 404: Generational Shenandoah [4] JEP 363: Remove the CMS Garbage Collector