ZGC Generational Internals
Purpose
Decide what a generational ZGC symptom is actually caused by — barrier cost, heap headroom, promotion rate, or a measurement that never looked at the right phase — using the mechanism rather than a remembered flag list. On JDK 25 there is no non-generational mode to compare against, so the generational behaviour is ZGC behaviour, and the tuning surface is small enough that every wrong move is a wrong model of the collector.
The failure this prevents is the confident no-op: reasserting a flag that was removed or is already the default, copying a heap multiplier from another service, or reporting a pause percentile computed from a log grep that misses one of the three STW phases. Each of these leaves the real problem undiagnosed while looking like a fix.
Workflow
- Establish the baseline before anything else.
-XX:+UseZGCalone is generational on JDK 25. Confirm ZGC is actually selected withjcmd <pid> VM.flags -all | grep -i UseZGC— not that a mode flag is present. - Read the flags off the running JVM, not from memory. Check
ZCollectionInterval,ZAllocationSpikeToleranceandZProactivewith-XX:+PrintFlagsFinalorjcmd <pid> VM.flags -allagainst the build in use; these defaults move between releases. - Capture every STW phase emitted by the target build. Log with
-Xlog:gc*,gc+phases=debug; validate the parser against real young and old cycles and do not assume one textual phase list survives releases. - Separate young and old-cycle evidence. Frequency is workload/ergonomic, not “continuous versus rare.” Correlate promotion/aging, live bytes, allocation and old-cycle triggers; temporal overlap alone does not prove promotion pressure.
- Classify allocation stalls from capacity signals. Join each stall to free/used/soft-max
heap, live set, page/large allocation, concurrent-cycle progress, allocation rate,
ConcGCThreads, CPU throttling and safepoints. Time-of-day clustering is context, not cause. - Attribute barrier cost from generated code/profile evidence. Fast paths are commonly
inlined and may not appear as named frames. Stores execute a barrier path broadly; only
some require remembered-set work. See
references/barriers-and-remembered-set.md. - Choose remediation by the proven bottleneck: reduce allocation/live set, restore CPU, add justified hard/soft heap headroom, control bursts/backpressure, or run a scoped heuristic experiment. There is no safe global order of flags.
Rules
- Do not add
ZGenerationalto JDK 24+: JEP 490 made it obsolete, with a warning while ignoring the value. Reproduced on Temurin 25.0.3+9. Expiration subsequently makes it unrecognized; test the exact upgrade build rather than treating acceptance as application. Historical JDK 21/22 generational selection requires the version-specific flag. Timeline: JEP 439 (JDK 21, opt-in) → JEP 474 (JDK 23, default) → JEP 490 (JDK 24, only mode). - Reasserting default-enabled
ZProactiveis not an allocation-stall fix. Inspect its effective value and cycle trigger before considering a change; proactive collection targets idleness/low allocation rather than guaranteeing recovery from sustained peaks. - Heap sizing is conditional on measured live set, allocation distribution, relocation progress, large pages/objects, concurrent CPU and burst duration. No collector-independent live-set multiplier (1.5×, 2.5× or 4×) predicts safety; derive and validate headroom under steady, peak and throttled conditions.
- A pause script that greps
"Pause Mark"reports an incomplete population —Pause Relocate Startis a real STW pause and is commonly omitted from diagrams. Every such script needs a sanity assertion that aborts when the sample count is zero. Omitting a phase can bias a percentile either way. Allocation stalls block allocating threads, not necessarily all application threads at a global safepoint; keep them separate. - The load-barrier fast path tests the pointer value with a bitmask, before any access to
the pointed-to object. Any explanation shaped like
if (obj.color != expected)inverts the dependency order that makes the mechanism safe. - An object's generation is represented in
ZPagemetadata on the inspected JDK 25 source, not inferred from a simple generation color in each oop. Pointer metadata and barrier masks evolve; quote exact bits only from the target source/build. - The remembered set is a bitmap (
ZBitMap,zRememberedSet.hpp) with one bit per potential object-field address — not G1's byte-per-card array. Do not describe it as a card table. - Legacy ZGC multi-mapping and JEP 490's removal of non-generational mode are distinct
changes. Never infer cgroup charge from old
psfolklore; measure RSS/PSS, heap andmemory.currenton the target build. - Confirm ZGC JFR events with
jfr metadata. JDK 25 includes the young/old/stall/page events plus relocation-set, statistics, thread-phase and uncommit events. There is nojdk.ZGCGarbageCollectionon that build, but do not turn this list into a cross-release allowlist. jcmd <pid> Thread.dumpis not a subcommand. UseThread.printorThread.dump_to_file -format=json— and neither measures CPU. For per-thread CPU usetop -H -p <pid>orpidstat -t -p <pid> 1.- Distinguish measured overhead/sizing evidence from estimates. Report an actual measurement with its build, workload and method; label unmeasured predictions as conditional.
Production acceptance
- Parse counts must reconcile with actual cycle IDs/generations; fail closed on unknown phase names, truncated/rotated logs and recording loss rather than reporting a perfect percentile.
- Exercise allocation spikes, old live-set growth, large objects, CPU quota/throttling and
SoftMaxHeapSizepressure. Verify stalls, achieved throughput and cgroup headroom together. - For any barrier/flag change, preserve correctness tests and compare CPU, allocation, throughput and tail latency across repeated runs on the exact JDK build.
References
- Cycles, logs and events — the phase sequence with STW phase families, the per-generation log format, the JFR event table and the recording and reading commands. Read when configuring ZGC logging, writing a pause-measurement script, or interpreting a generational ZGC log.
- Barriers and the remembered set — the load and store barrier fast paths, the remembered-set bitmap, its double buffering, and how to attribute barrier cost in a profile. Read when barrier overhead is suspected, when promotion rate is in question, or when documenting the mechanism internally.
Authoritative sources: JEP 439, JEP 474, JEP 490, and the OpenJDK 25 ZGC sources.