What does the JVM flag -XX:MaxGCPauseMillis actually do, and what happens if you set it to 5 milliseconds on a G1 heap?
answer
- Soft goal, not a guarantee; G1 default 200 ms
- Target sizes the collection set via the pause-prediction model
- Lower target → smaller young gen → more pauses, more promotion
- Too low → mixed GCs fall behind → evacuation failure → full GC (worse)
- Single-digit ms is a collector choice (ZGC/Shenandoah), not a flag
basics
~20 sIt is a soft goal, not a guarantee. G1 uses it to size each collection set: to hit a smaller target it collects less per pause, so pauses become shorter but more frequent, throughput drops, and old-generation reclamation can fall behind. An unrealistic 5 ms mostly costs throughput without delivering 5 ms.
solid answer
~60 s`-XX:MaxGCPauseMillis` (default 200 for G1) is a **target the collector aims at**, not a limit it enforces. G1 keeps a model of how long evacuating a given amount of live data takes, and before each collection chooses a *collection set* — the young regions plus, in mixed collections, a batch of old regions — predicted to fit inside the target. Drive the target down and G1 responds by collecting *less per pause*: smaller young generation, fewer old regions per mixed collection. The consequences chain: - More collections per second, so more fixed per-pause overhead and lower throughput. - A smaller young generation means objects age less before collection, which raises premature promotion and old-generation pressure. - Fewer old regions per mixed cycle means reclamation can lose the race with allocation, producing evacuation failures and the very long full compactions you were trying to avoid. And there is a floor: some pause work (root scanning, fixed phases) does not shrink. Asking for 5 ms on G1 mostly buys the downsides. If you genuinely need single-digit-millisecond pauses, that is a *collector* decision — ZGC or Shenandoah — not a target you can request from G1.
code
text · 9 lines-XX:MaxGCPauseMillis=200 // soft pause goal (G1 default)
-XX:G1NewSizePercent=5 // young-gen floor, % of heap
-XX:G1MaxNewSizePercent=60 // young-gen ceiling, % of heap
-XX:InitiatingHeapOccupancyPercent=45 // when concurrent marking starts
-XX:G1MixedGCCountTarget=8 // how many mixed GCs a reclamation phase spreads over
// A tight MaxGCPauseMillis squeezes the young generation toward the floor
// and shrinks each mixed collection set - which is exactly how old-gen
// reclamation ends up losing the race with allocation.go deeper
Know it is a target rather than a guarantee, that G1's default is 200 ms, and that lowering it makes pauses shorter but more frequent.
Explain the collection-set mechanism and the chain from a tight target to premature promotion and lower throughput.
Show the failure mode — mixed collections falling behind, evacuation failure, worse tail — and tune in measured steps against an SLO.
Decide when the requirement is structural rather than a tuning knob, and own the tradeoff of moving the fleet to a concurrent collector versus living with the default.
## A goal, not a contract The single most important thing to say about `-XX:MaxGCPauseMillis` is that it is a **soft goal**. Nothing in the JVM aborts a collection that overruns it. G1 (and, more weakly, the Parallel collector) treats it as the objective its heuristics optimise toward, and it will regularly miss it — during a full compaction, when a large amount unexpectedly survives, or when the host is starved of CPU. ## How G1 uses it G1 maintains a *pause-time prediction model*: statistics gathered from previous collections about how long it takes to scan roots, copy a given volume of live data, update remembered sets and finish up. Before each collection it uses the model to pick a **collection set** whose predicted cost fits the target: - **Young collections**: G1 adaptively sizes the young generation between `-XX:G1NewSizePercent` and `-XX:G1MaxNewSizePercent` of the heap. A tighter pause target pushes the young generation smaller so fewer regions are evacuated per pause. - **Mixed collections**: after concurrent marking identifies garbage-rich old regions, G1 adds them in batches (bounded by `-XX:G1MixedGCCountTarget` and friends), again sized to fit the target. So the target does not make the collector faster; it changes *how much work it attempts at once*. ## The chain of consequences when the target is too low 1. **More frequent collections.** The same allocation rate against a smaller young generation means more pauses per second. Each pause carries fixed costs — reaching a safepoint, scanning thread stacks and other roots — that do not shrink with the collection set. Total GC overhead rises and throughput falls. 2. **Premature promotion.** A smaller young generation gives objects less time to die before a collection arrives, so more of them survive one collection, then another, and get promoted. Objects that would have died in eden now land in the old generation. 3. **Old-generation pressure.** More promotion means more work for concurrent marking and mixed collections, which the same tight target now forces to be smaller batches. Reclamation can fall behind allocation. 4. **Evacuation failure.** When G1 has no free region to copy survivors into, it falls back to a stop-the-world full compaction lasting hundreds of milliseconds to seconds — vastly worse than the 200 ms default you were trying to beat. That is the irony worth stating in an interview: an aggressive pause target can *increase* worst-case pause time. ## The floor Even with an empty collection set there is irreducible work: reaching a safepoint (time-to-safepoint depends on application threads, not the collector), scanning thread stacks and other roots, and fixed start/end phases. On a large heap with thousands of threads that floor alone can exceed a few milliseconds. Requesting 5 ms does not make roots scan faster; it just makes G1 attempt an impossibly small collection set and keep missing. ## What to do instead - **Start with the default** (200 ms) and measure. Change it only when pause percentiles from GC logs, correlated with your latency SLO, say you must. - **Move in modest steps.** Going 200 → 100 → 50 with a measured workload at each step tells you where throughput starts to hurt and where returns stop. - **Give G1 headroom.** Many pause problems are heap-headroom or allocation-rate problems wearing a costume; a target cannot conjure free regions. - **Change collector when the requirement is structural.** If the SLO genuinely demands single-digit-millisecond pauses at multi-gigabyte heaps, that is ZGC's or Shenandoah's design point — collectors whose pauses are bounded by design rather than by a heuristic target — and you accept their throughput and footprint cost instead. - **Never combine a very low target with a heap that runs near its ceiling.** That is the recipe for continuous evacuation failure. ## On other collectors The Parallel collector accepts `-XX:MaxGCPauseMillis` too and will shrink generations toward it via adaptive sizing, but it has no incremental old-generation mechanism, so its full collection remains proportional to the live set and the target is largely aspirational there. ZGC and Shenandoah do not need it in the same way: their pauses are short by construction, and the tuning lever is instead how aggressively collection is scheduled relative to allocation. Setting a target that conflicts with `-XX:GCTimeRatio` (the throughput goal) simply means the collector cannot satisfy both, and pause tends to win — at throughput's expense.
- A team lowered the pause target from 200 ms to 20 ms and now sees occasional multi-second pauses. Explain how that happened.The tight target forced G1 into small collection sets: a smaller young generation, more frequent collections, more premature promotion, and fewer old regions reclaimed per mixed collection. Old-generation occupancy grew faster than mixed collections could reclaim it, until evacuation had no free region to copy into and G1 fell back to a stop-the-world full compaction. The aggressive target produced worse worst-case pauses than the default.
- If you truly need pauses under 10 ms on a 32 GB heap, what do you change?The collector, not the target. ZGC and Shenandoah do their marking and relocation concurrently, so their pauses are bounded by construction and largely independent of heap size, which is the property a G1 pause target cannot manufacture. You accept the cost — load or read barriers, extra CPU for concurrent work, and more heap headroom — and validate against the real workload before committing.
saying these in an interview costs you the question
- Describing the flag as a hard limit the JVM enforces per collection.
- Assuming a lower target always yields lower pauses, with no downside.
- Not connecting a tight target to premature promotion and evacuation failure.
- Using an aggressive target as a substitute for choosing an appropriate collector or giving the heap headroom.
- Setting it without measuring pause percentiles before and after.