Quick answer
Generational ZGC in Java 25 performs expensive heap work concurrently to keep stop-the-world pauses very short, with a throughput and CPU tradeoff. Diagnose it through allocation rate, young and old collection progress, live-set and heap headroom, concurrent GC CPU, relocation, allocation stalls, process memory, and the service latency objective. Low pause does not mean zero pause, zero overhead, or faster application code.
Select ZGC because a measured latency objective justifies the collector tradeoff, not because it sounds newer. Give it enough heap and CPU headroom to complete concurrent work before allocation exhausts available space, and verify the whole Spring order service under representative traffic.
Learning objectives
- Interpret Java 25 generational ZGC evidence, including concurrent cycles, allocation headroom, stalls, and memory return behavior.
- Compare ZGC with G1 using workload objectives and end-to-end evidence rather than pause time alone.
Prerequisites
Read JVM GC Logs and Pause Diagnosis and G1 Diagnosis and Tuning so collector selection is grounded in the same allocation, live-set, CPU, and user evidence.
Evidence and system boundary
ZGC uses concurrent marking, relocation, reference processing, and generational collection so application threads stop briefly for limited coordination. It still consumes CPU and memory bandwidth. If the application allocates faster than ZGC can reclaim space, allocation stalls can stop allocating threads until memory becomes available.
This is an explanatory Java 25 launch example, not a production command that was executed:
java -XX:+UseZGC -Xmx8g -Xlog:gc*,gc+heap=info:file=zgc.log:time,uptime,level,tags -jar order-service.jar
The heap value is illustrative, not a sizing recommendation. Container memory must also cover native memory, thread stacks, code cache, GC structures, libraries, and operating-system overhead.
Read generational ZGC evidence
Java 23 made generational mode the default for ZGC, and Java 24 removed the non-generational mode. Java 25 material should therefore teach generational ZGC directly. Young collections target short-lived objects; old collections reclaim longer-lived data. Examine collection causes, cycle duration, allocation, reclaimed space, live-set trend, and whether concurrent work completes with adequate headroom.
An allocation stall is a capacity symptom. Determine whether the workload spiked, the live set grew, heap sizing is insufficient, CPU is throttled, or concurrent GC cannot keep pace. Increasing -Xmx may add runway but also increases process memory and may only delay a leak. Reducing allocation or retention is often the stronger fix.
ZGC can return unused memory subject to its heuristics and configured limits. A larger reserved maximum is not the same as committed or resident memory, and a process RSS graph is not a heap graph. Compare heap, committed memory, RSS, container working set, and cgroup limit before drawing a conclusion.
Pause time is only one objective. Measure application throughput, request tails, GC CPU, allocation stalls, memory footprint, warmup, and dependency demand. A collector that lowers pauses while raising CPU throttling can worsen user latency in a constrained container.
G1 or ZGC
G1 is the balanced default for many server workloads and performs incremental reclamation with observable young and mixed pauses. ZGC targets very low pauses with more concurrent work. Use a stable workload and equal service objectives to compare them. Keep application version, heap policy, container resources, traffic, dependency state, and warmup comparable.
Do not migrate during an unexplained incident merely to suppress visible pauses. First determine whether GC materially contributes to the SLI. A collector switch changes memory, CPU, logging, and operational behavior and needs a canary plus rollback.
Production failure scenario
A latency-sensitive Spring order service uses ZGC. After lowering container CPU limits, pauses remain sub-millisecond, yet checkout p99 and allocation stalls rise. GC and application threads compete for throttled CPU, so concurrent work loses headroom.
The team restores the approved CPU budget in a canary instead of increasing heap. Allocation stalls fall, throughput and p99 recover, and process memory remains bounded. It verifies new orders and ambiguous timeouts from durable state. Short pauses never disproved a GC capacity problem; CPU and stall evidence revealed it.
Common misconceptions
- ZGC does not mean zero pause; Java 25 documentation describes very short pauses, not their absence.
- Heap-size-independent pause targets do not make total CPU or memory cost independent of heap and workload.
- A low pause percentile does not prove good throughput or sufficient allocation headroom.
- Generational ZGC is not an experimental collector in Java 25; some adjacent JFR profiling events remain experimental.
- A collector switch cannot repair application retention, unbounded caches, or undersized dependencies.
Decision checklist
- State the latency and throughput objectives that justify evaluating ZGC.
- Use the exact Java 25 update and record effective heap and collector flags.
- Measure allocation, live set, cycle duration, GC CPU, stalls, RSS, and throttling.
- Budget container memory beyond the managed heap.
- Keep a G1/ZGC comparison workload and warmup state comparable.
- Treat a collector switch as a canaried release with rollback.
- Reject pause-only conclusions when CPU, throughput, or business evidence disagrees.
- Verify fresh user outcomes and durable order state after the change.
Related reading
Continue with Java Heap Dumps and Memory Leak Analysis and compare G1 Diagnosis and Tuning. The complete sequence lives in the Java Concurrency course and topic cluster.
Official sources
- Java 25 available collectors
- Java 25 HotSpot GC Tuning Guide
- JEP 474: Generational ZGC by default
- JEP 490: Remove the non-generational ZGC mode
Official sources accessed August 19, 2026. Recheck collector behavior, supported flags, and container ergonomics against the exact Java runtime before use.