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=-Xmxto 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
- Java memory & GC — the map — the group overview.
- GC principles & algorithms — the shared machinery this table recombines; read it first if the columns look like magic.
- JVM memory anatomy — the heap layout each collector carves up differently.
- JVM implementations compared — the cross-runtime view: OpenJ9 policies, Azul C4.
- jstat, jmap, JFR & Mission Control — the observation tools the tuning order above leans on.
- Performance engineering — GC choice as one lever in a wider latency budget.
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