Walk through the phases of one garbage-collection cycle in HotSpot's Shenandoah collector, and state precisely which parts stop the application threads.
answer
- Init Mark → Conc Mark → Final Mark → Conc Evac → Update Refs → Cleanup
- Pauses: Init Mark, Final Mark, Init/Final Update Refs
- Collection set chosen at Final Mark: most garbage first
- Empty regions reclaimed with no copying
- 'Pause Degenerated GC' in the log = lost the race
basics
~20 sInit Mark (stop-the-world: scan roots, arm barriers), Concurrent Mark, Final Mark (stop-the-world: drain marking buffers, choose the collection set, evacuate root-referenced objects), Concurrent Cleanup of empty regions, Concurrent Evacuation, Init Update Refs (brief stop), Concurrent Update References, Final Update Refs (stop-the-world: fix remaining roots, recycle regions).
solid answer
~1 minA Shenandoah cycle alternates short pauses with long concurrent phases: 1. **Pause Init Mark** *(STW)* — scan thread stacks and other roots, set the global GC state so barriers activate. Cost tracks the root set. 2. **Concurrent Mark** — trace the live object graph while the application runs; a snapshot-at-the-beginning write barrier records references the application overwrites so nothing live is missed. 3. **Pause Final Mark** *(STW)* — drain the recorded mutation buffers, finish marking, compute per-region liveness, pick the **collection set** (most-garbage regions first), and evacuate the objects directly reachable from roots. 4. **Concurrent Cleanup** — immediately recycle regions found to contain no live objects at all; these cost nothing to reclaim. 5. **Concurrent Evacuation** — copy live objects out of collection-set regions into fresh regions, with mutators assisting via load reference barriers. 6. **Pause Init Update Refs** *(STW, very short)* — a checkpoint that ensures all threads have finished evacuation work. 7. **Concurrent Update References** — walk the heap rewriting references that still point into evacuated regions. 8. **Pause Final Update Refs** *(STW)* — update the remaining roots and recycle the collection-set regions. 9. **Concurrent Cleanup** — reclaim the freed regions. Only steps 1, 3, 6 and 8 stop the world, and each does root-set-sized work.
code
text · 9 linesGC(12) Pause Init Mark 0.385ms
GC(12) Concurrent marking 71.802ms
GC(12) Pause Final Mark 0.712ms
GC(12) Concurrent cleanup 0.104ms
GC(12) Concurrent evacuation 31.559ms
GC(12) Pause Init Update Refs 0.089ms
GC(12) Concurrent update references 28.913ms
GC(12) Pause Final Update Refs 0.201ms
GC(12) Concurrent cleanup 0.135msgo deeper
Remember the alternation: short pause, long concurrent phase, repeated for marking, evacuation and reference updating.
Name the phases in order and identify the four stop-the-world points and what each does.
Map log lines to diagnosis — pause length to root set, cycle duration to allocation rate and headroom, degenerated GC to a lost race.
Use the phase structure to reason about the collector's resource profile when setting adoption criteria and CPU headroom for a fleet.
## The shape of a cycle Shenandoah's cycle is best remembered as three concurrent workloads — **mark**, **evacuate**, **update references** — each fenced by short stop-the-world points that exist only to take a consistent snapshot of the roots or to flip global state safely. Nothing in a pause is proportional to heap size or live data. ## 1. Pause Init Mark (stop-the-world) All threads reach a safepoint. The collector scans the roots — thread stacks and registers, static fields, JNI handles — marking the objects they reference and queuing them for tracing. It also flips a global GC state word that each thread reads, which activates the barriers in compiled code. Duration is proportional to the number and depth of thread stacks, so a process with thousands of threads has measurably longer init-mark pauses than one with a handful. ## 2. Concurrent Mark Application threads resume; collector threads trace the object graph from the marked roots. Because the application keeps mutating references during the trace, the collector could otherwise miss an object that gets moved from an untraced location to an already-traced one. Shenandoah handles this with a **snapshot-at-the-beginning** write barrier: when the application overwrites a reference field, the previous value is recorded into a thread-local buffer so the collector still traces it. The effect is that the collector marks everything live at the start of the cycle, plus anything allocated during it — newly allocated objects are treated as live for this cycle and considered next time. This is the longest phase in most cycles, and its cost scales with live data. ## 3. Pause Final Mark (stop-the-world) Another safepoint. The collector drains the SATB buffers, completes marking, and then does the planning work for the rest of the cycle: - compute how much live data each region holds; - select the **collection set**: the regions whose evacuation yields the most free space for the least copying; - pre-allocate the regions evacuation will copy into; - evacuate the objects directly referenced from roots, so that after the pause every root already satisfies the to-space invariant. Again, the pause cost tracks roots and buffers, not the heap. ## 4. Concurrent Cleanup (immediate garbage) Regions found to contain **no** live objects are recycled straight away without copying anything. On workloads where most objects die young this is a large share of the reclaimed memory and is essentially free. ## 5. Concurrent Evacuation Collector threads copy live objects out of the collection set into the newly allocated regions. Simultaneously, application threads that load a reference into the collection set may copy the object themselves via the load reference barrier, and they self-heal the field they loaded from. Two threads racing to copy the same object are resolved by a compare-and-swap on the forwarding word. This is the phase that makes the collector unusual: compaction, historically a stop-the-world operation, is happening while requests are being served. ## 6. Pause Init Update Refs (stop-the-world) A very short checkpoint — often a fraction of a millisecond. It mostly ensures every thread has finished its evacuation work and flips state before the reference-updating phase begins. ## 7. Concurrent Update References The collector walks the heap linearly, rewriting any references that still point into evacuated regions so they point at the to-space copies. Much of this work has already been done opportunistically by self-healing barriers during evacuation; this phase guarantees completeness. It scales with live data, not with the collection set. ## 8. Pause Final Update Refs (stop-the-world) Update the remaining roots (which can only be fixed at a safepoint), then recycle the collection-set regions — their contents are now unreachable because every reference has been rewritten. ## 9. Concurrent Cleanup The freed regions are returned to the allocator, and their space becomes available for allocation. ## Reading it in a log With `-Xlog:gc` each phase appears on its own line, pauses labelled `Pause …` and concurrent phases labelled `Concurrent …`. The habit worth building is to check (a) that pauses stay sub-millisecond to low-single-digit milliseconds, (b) that concurrent phases complete before the heap fills, and (c) that no lines say `Pause Degenerated GC` or `Pause Full` — those indicate the collector lost the race against allocation and had to finish the work with the world stopped. ## Why the phase list is worth memorising Because every operational question maps onto it: long pauses ⇒ look at the root set (thread count, stack depth); long cycles that do not keep up ⇒ look at allocation rate, heap headroom, and concurrent thread count; degenerated GCs ⇒ the cycle started too late for the allocation rate. The phase names in the log are the diagnostic vocabulary.
- Why is a separate 'update references' phase needed if the load barrier already corrects references as threads use them?The barrier only fixes references that application threads actually load during the cycle. References in cold data structures may never be touched, so they would keep pointing into evacuated regions. The concurrent update-references phase walks the heap to guarantee completeness, which is what makes it safe to recycle the collection-set regions at the end.
- What determines which regions end up in the collection set?Liveness computed during marking. The collector prefers regions with the most garbage, because evacuating them frees the most space for the least copying, and it bounds the set so that the evacuation work fits within the free space and time available. Regions that are entirely garbage are not evacuated at all — they are recycled immediately.
saying these in an interview costs you the question
- Listing evacuation as a stop-the-world phase
- Forgetting the update-references phase, or thinking barriers alone make it unnecessary
- Claiming the collection set is chosen at the start of the cycle rather than after marking
- Describing the pauses as proportional to heap size instead of root-set size