Your service allocates request objects steadily. Most requests finish quickly, yet the heap sometimes fills and the JVM pauses to recover space. Which bytes can the collector reuse, and what shape will the remaining free space have?
Earlier lessons introduced GC roots, collector strategies, generations, write barriers, and safepoints separately. Here we connect them into one collection path. First discover which objects must survive. Then make the rest of the heap useful for future allocations.
Tracing answers which objects are live; sweeping, compaction, and evacuation answer what to do with heap space afterward. This distinction lets us compare collectors without assuming that every tracing pass moves objects.
Start from roots, then follow references
Imagine a stack reference to object A. A refers to B and C. A static field refers to D, which refers to E. Two more objects, F and G, refer to each other but have no path from either root.
A GC root is a starting reference the collector must account for, including references associated with thread execution and JVM runtime state. The collector follows references transitively. The objects it discovers form the live set: the reachable object graph for that collection’s scope and snapshot. In this example A–E are live; F and G are unreachable.
A cycle is garbage when no path from a GC root reaches it. Merely having an incoming reference does not keep an object alive. This is why reference counting alone cannot describe Java’s tracing collectors.
The roots must be accurate even when Java methods run as optimized machine code. HotSpot’s compiled-frame metadata, including OopMaps, identifies locations that contain object references at suitable execution points. Safepoint coordination and related mechanisms give the collector a state it can inspect consistently. The safepoints lesson explains that execution-engine side; here its result becomes the starting edge of the object graph. Actual root processing and concurrent collection are more involved than the stationary graph in the figure.
Sweep: recover holes without moving survivors
In a textbook mark-sweep pass, the collector marks objects reached during tracing. It then scans the relevant heap space and makes unmarked objects available for reuse. A, B, and C stay at their old addresses in this simplified row; the former garbage positions become holes.
Keeping survivors in place avoids moving them and repairing their references for this step. The resulting free space may be fragmented: several holes can add up to more bytes than an allocation needs while none is large enough as one contiguous area. Allocators can maintain free lists and choose fitting holes, but fragmented space complicates allocation and can eventually require other recovery work. The exact outcome depends on the collector and allocator; “mark-sweep” is a teaching model, not a description of every HotSpot phase.
Compact: move survivors into a dense area
Mark-compact starts with liveness information, computes new positions for survivors, moves them into a denser arrangement, and leaves a larger contiguous free range. If A, B, and C occupy the front of our illustrative space after compaction, an allocator can advance a bump pointer through the free tail for suitable allocations. A bump pointer records the next free address and advances after reserving space; it does not mean every Java allocation in every collector uses one global pointer.
Moving an object is more than copying its bytes. A stack slot, static field, or field in another object that pointed to the old address must eventually resolve to the new address. Collector algorithms coordinate those reference updates with the move; some use forwarding information or barriers during a transition. The exact mechanism varies, but leaving an ordinary reference pointing at abandoned storage would break the program.
Evacuate: preserve survivors elsewhere
A copying or evacuating collection selects a source space, finds the objects that must survive, and copies or moves them into destination space. Once all required survivors and references are handled, the source space can be reused as a unit. That action combines reclamation and compaction: garbage is left behind while survivors arrive packed in the destination.
This gives a useful intuition: cost ≈ live data copied. If little survives, there is relatively little survivor data to move. It is not a complete pause-time or CPU equation. Root processing, remembered sets, reference updates, marking, bookkeeping, coordination, concurrent work, and failed or constrained evacuation also contribute. Destination capacity and spare heap headroom matter because survivors need somewhere to go.
The generational hypothesis says many newly allocated objects die young. When that pattern holds, evacuating a young collection set is attractive: discard the many dead objects with the source space and copy the smaller survivor set. It is a workload tendency, not a promise about every application or object.
G1 puts the pieces into regions
On server-class machines, G1 is the default collector in JDK 25. HotSpot’s G1 divides the heap into regions, tracks generations and remembered information, and selects a collection set of regions. It evacuates surviving objects from selected regions into destination regions; old-region selection uses liveness and expected collection efficiency. G1 also performs marking and other concurrent or pause-time work. Its region and evacuation model is a concrete bridge from the textbook techniques, not a claim that G1 runs one simple whole-heap mark-sweep or copying pass.
This is also why a collector name does not imply one textbook algorithm. Real collectors combine tracing, marking, region selection, copying or compaction, remembered metadata, barriers, and concurrent work to balance throughput, pause latency, CPU use, memory footprint, allocation speed, spare headroom, and predictability. Improving one measure can consume resources that another measure needs. G1’s pause goals are targets, not guarantees.
Read allocation and survival separately
For a running service, distinguish three signals. Allocation rate is how quickly new objects consume space. Survival rate is the fraction that remains reachable when a collection examines a cohort. Live-set size is the reachable data a collector must preserve, possibly across several areas of the heap. They answer different questions: how quickly collection pressure arrives, how much evacuation work a young collection may do, and how much space and processing a persistent reachable graph requires.
Use GC logs and workload latency together to ask whether pauses coincide with user-facing slowdowns. Allocation profiles help identify producers; heap paths to roots help investigate retained objects. One heap occupancy number cannot by itself identify a leak, and one pause cannot establish a stable trend. Before changing collector or heap settings, identify whether the pressure comes from creation, survival, retained live data, or too little spare space for the current collection pattern.
Check your reasoning
If tracing marks A, B, and C as live, must the collector move them?
No. Tracing establishes liveness. A sweeping strategy can reclaim unmarked positions while A, B, and C stay at their addresses; compaction or evacuation may move survivors for a different free-space result.
F and G refer to each other but no root reaches either. Can both be garbage?
Yes. The reference cycle has no path from a GC root, so tracing from roots does not include either object in the live set.
Two services allocate at the same rate. Must their GC work be similar?
No. Different survival, live-set size, reference connectivity, and destination headroom can make collection costs and outcomes very different.
The complete collection path
Roots and reference metadata allow tracing to discover a live set. The collector then chooses how to reclaim and reshape space: leave holes, move survivors together, or evacuate survivors from selected source areas. Moving requires a valid reference protocol; low survival can make evacuation particularly useful. That chain explains why collector behavior depends on the live graph and available space as much as on bytes allocated per second.
Next, Generational GC mechanics will follow a young allocation through Eden and survivor regions, then through promotion when it remains live. It will make the remembered-set and write-barrier work behind cross-generation references concrete. Those are the details needed to understand why a young collection can trace a subset of the heap safely.
Sources and implementation boundaries
Based on Daily JVM Byte #22 and the reviewed lesson requirements. Object graphs, heap rows, and costs are explanatory models; no GC workload or command was executed for this lesson. Root metadata and G1 details describe HotSpot on JDK 25 rather than a universal JVM implementation. Collector phase timing, reference processing, and allocation policy vary by collector and configuration.