ZGC vs. G1: Picking the Right Garbage Collector for High-Throughput

Compare ZGC and G1 by workload and JDK version. Separate collector design goals from measured latency, throughput, and resource costs.

ZGC vs G1 is a trade-off between pause-time goals, throughput, CPU, and memory headroom. The right choice depends on the JDK version and the workload. This guide explains the mechanisms and a measurement process, without treating illustrative workloads as benchmark results.

Start with the behavior you need to improve: application tail latency, GC pauses, allocation stalls, or total resource cost. Then compare collectors under the same load. Task scheduling and cancellation are separate concerns, covered in Java structured concurrency.

Understanding the Fundamentals

Start with the collectors’ mechanisms, then use measurements to check how those mechanisms affect the application.

G1 Garbage Collector

G1 is the default collector on typical server-class HotSpot configurations. It uses regions, generations, concurrent marking, and stop-the-world evacuation. Its region size depends on heap sizing and JDK ergonomics.

  • Region-based heap: Equally sized regions, with size selected for the configured heap and JDK.
  • Generational model: Young & old collections are separate.
  • Concurrent marking: Identifies live objects while the application continues running.
  • Mixed collections: Cleans both young and old regions based on garbage density.
  • Stop-the-world phases: Needed for evacuation and compaction operations.

G1 uses a configurable pause-time goal. A goal is not a maximum: the collector may exceed it under allocation pressure or other workload conditions. See the G1 tuning reference for the distinction.

Z Garbage Collector (ZGC)

ZGC became production-ready in JDK 15 and was designed with a single goal: keep pause times consistently tiny, independent of heap size. Its key innovations are:

  • Colored pointers: Metadata is embedded inside object references.
  • Load barriers: Every object access checks and fixes stale references on the fly.
  • Almost fully concurrent: Marking, relocation, and remapping happen while the app runs.
  • Uniform treatment of objects: JDK 17 ZGC was non-generational. As of JDK 24, ZGC is generational only.

ZGC is designed for low pause times, but a short GC pause is not an application latency guarantee. Allocation stalls, CPU contention, I/O, and safepoints can still affect requests. See the JDK 25 ZGC guide for its design and tuning boundaries.

Architecture and Design Differences

How G1 Works Internally

G1’s core cycle includes:

  • Young Collections: STW events to clean Eden and Survivor regions.
  • Concurrent Marking: Identifies live objects across the heap.
  • Mixed Collections: Evicts garbage-heavy old regions.
  • Full GC: A stop-the-world fallback whose duration depends on heap and workload.

G1 behavior depends on allocation, live data, available CPU, and evacuation work. Heap size alone does not determine whether the collector meets a service objective.

How ZGC Operates

ZGC’s architecture minimizes pause time by performing almost everything concurrently:

  • Object relocation happens while mutator threads run.
  • Remapping is incremental and piggybacks on normal pointer loads.
  • ZGC targets very short stop-the-world phases. This does not bound total application latency.

ZGC is a useful candidate when GC pauses contribute to a latency problem. Confirm the benefit with application-level measurements rather than choosing a collector from heap size alone.

Performance trade-offs to measure

Measure application P95/P99 latency, achieved throughput, GC pause distribution, allocation stalls, process RSS, and CPU at the same offered load. A reduction in pause time can be offset by CPU contention or insufficient allocation headroom.

Collector design goals are useful for choosing a candidate. They do not predict a numerical improvement for a particular service. A useful comparison needs a reproducible harness and the underlying results.

A comparison process

Use the same JDK build, application, dataset, container limits, warm-up, and load profile. Change the collector first. Record steady load, bursts, recovery, and sustained overload separately. Publish the setup and raw results before drawing numerical conclusions.

For an HTTP service, correlate GC events with request latency. For a batch job, compare total runtime and cost. For a cache-heavy process, measure the live set and allocation rate. These are candidate workloads, not benchmark results.

Which One Should You Choose?

Pick ZGC if:

  • Low GC pause time is important to your measured latency objective.
  • The workload has enough CPU and memory headroom for concurrent collection.
  • Traffic is bursty.
  • Measurements show that reducing GC pauses improves the service objective.

Pick G1 if:

  • Throughput matters more than latency spikes.
  • G1 meets the latency objective within the available memory and CPU budget.
  • You run inside strict container quotas.
  • The workload is standard request/response or batch processing.

G1 is a reasonable starting point for many server workloads. Test ZGC when pause sensitivity justifies the comparison, and retain the option to use G1 if its measured behavior better fits the service.

Tuning Recommendations

G1 Basics

-XX:+UseG1GC
-Xms16g -Xmx16g
-XX:MaxGCPauseMillis=200

Start with the defaults and change one setting at a time after measuring a specific problem.

ZGC Basics

-XX:+UseZGC
-Xms16g -Xmx16g

Start with the collector defaults and a heap size that leaves allocation headroom. JDK 25 ZGC is generational without a ZGenerational flag. Treat ConcGCThreads as an advanced override and avoid copying a fixed value without measuring it.

Validate a collector change before rollout

Replay a representative workload or canary a small amount of traffic. Keep a rollback flag and compare both request latency and resource use. Adopt a collector only when it improves the measured service objective within the available CPU and memory budget.

Record the JDK, configuration, traffic, and capacity budget alongside the results so a later comparison can distinguish a collector effect from a workload or infrastructure change.

Final Thoughts

There’s no single “best” garbage collector. The right choice depends entirely on your workload.

  • ZGC targets low GC pause time, with workload-dependent CPU and allocation-headroom costs.
  • G1 is a practical baseline. Compare measured latency, throughput, and resource cost before changing it.
Share
Prabhat Kashyap

Prabhat Kashyap

Senior Technical Architect at HCLTech · working with Leonteq Security AG

I have 10+ years of experience building distributed systems and fintech platforms. I write about practical, non-obvious engineering details that official documentation often skips.

Newsletter

Get new engineering deep dives

Scala, JVM, AI, and backend systems. I send practical articles when there is something worth reading.

Join the newsletter