core23 min studyLesson 126 in Engineering

Tracing collection: find the objects reachable from roots

Compute a reachable object graph, compare marking with reclamation, and diagnose retained memory that a collector cannot discard.

On this page

What you’ll understand

  • Find the transitive closure of an explicit root set.
  • Explain how tracing can reclaim an unreachable cycle.
  • Distinguish allocation rate, live payload, retained heap capacity, and process memory.

A tracing collector starts from the references the runtime treats as roots and follows object references. Objects discovered this way must remain available to the program. An unreachable cycle need not remain retained merely because its objects point to one another. The central idea is global reachability from roots, followed by a reclamation mechanism that is safe under the runtime's execution model.

Compute the graph closure

Represent each allocated object as a node and each traced strong reference as a directed edge. Roots can include references reported by stacks, registers, globals, and runtime handles, according to the implementation. Start with their targets, then repeatedly follow outgoing edges until no new node is discovered. A cycle reachable from a root remains live under this criterion. A cycle disconnected from all roots does not. This is different from reference counting's local incoming-edge total, which cannot by itself distinguish those two situations.

A collector needs cooperation from the language implementation to identify references correctly. A precise runtime knows which fields and execution locations contain managed references; a conservative approach can treat some pointer-like patterns as possible references and retain additional objects. Native handles, interior pointers, and runtime-specific weak or finalization semantics complicate the simple graph. Our exercises use precise strong edges and no finalizers. State those assumptions before claiming that every unreachable object will be reclaimed immediately or that a hidden native address is automatically understood by the collector.

L0=R,Lj+1=Lj∪{v:∃u∈Lj, (u,v)∈E},L=⋃j≥0LjL_0=R,\qquad L_{j+1}=L_j\cup\{v:\exists u\in L_j,\ (u,v)\in E\},\qquad L=\bigcup_{j\geq0}L_j
R is the finite set of objects directly designated by roots, E contains traced strong edges, and L is their reachable closure. The expression is a graph model with no weak-reference or finalization exceptions; j counts expansion rounds rather than elapsed seconds.↔ Scroll wide equations horizontally.

Marking and reclaiming are separate mechanisms

A simple stop-the-world mark-sweep collector first marks reachable objects while application mutation is stopped, then makes unmarked storage available for reuse. Moving collectors instead relocate some surviving objects and update the references needed to find them. Not every tracing collector moves objects, and not every phase must stop application threads. Concurrent collection needs barriers and coordination to keep the graph argument valid while the application changes it. These implementation choices affect pauses, CPU cost, fragmentation, and how native code may hold addresses.

Reclaiming an object for the runtime allocator does not necessarily return the corresponding physical pages to the operating system. The runtime may retain heap capacity for later allocations, and process memory also includes stacks, native libraries, code, metadata, and external buffers. Compare metrics with matching boundaries. A falling live-object total alongside stable resident memory is not sufficient evidence that collection failed. Conversely, increasing live payload after repeated collections suggests a retention path or workload change that more frequent collections alone cannot remove.

Meligible=∑v∈H∖Lsv,Mlive=∑v∈LsvM_{\rm eligible}=\sum_{v\in H\setminus L}s_v,\qquad M_{\rm live}=\sum_{v\in L}s_v
H is the set of allocated objects in the simplified collection scope, L is the reachable subset, and s_v is payload bytes. Eligibility assumes no finalization or special retention rules. These sums do not predict resident memory or when allocator pages are released to the operating system.↔ Scroll wide equations horizontally.

Measure retention separately from allocation churn

A service can allocate many short-lived temporary objects while maintaining a modest live heap. Another can allocate slowly but keep every request in a global history list, producing steadily increasing live memory. The first stresses allocation and collection throughput; the second has an application retention policy. To investigate, compare allocation profiles with post-collection live-object paths under the same workload. Identify which root keeps an unexpected object reachable. Removing a registry entry, listener, or cached result may solve the cause more directly than changing collector settings.

The probability lab approximates the count of surviving equal-sized independent objects, rather than traversing a graph. Set n=20 and survival probability p=0.25: predict a mean of five survivors and variance 3.75. Increasing p raises the mean even if the total number allocated remains fixed. If each survivor occupies 64 bytes, the expected payload is 320 bytes in this artificial cohort. Real object reachability is often highly correlated through shared graph structure. Use the lab to separate survivor fraction from allocation count, then explain why it cannot decide which nodes a tracing collector must mark.

A reachable cycle and an unreachable cycle

Six objects have payloads A=32, B=32, C=64, D=64, E=64, and F=32 bytes. The sole root targets A. Edges are A→B, B→C, C→B, D→E, and E→D. F has no edges.

  1. Start at A and follow its edge to B, then B to C. C returns to an already discovered B, so the reachable closure is exactly {A,B,C}.

  2. The live payload is 32+32+64=128 bytes. D and E form an unreachable cycle, and F is unreachable on its own; their combined payload is 64+64+32=160 bytes.

  3. The total allocated payload is 288 bytes. In this precise strong-edge model with no finalizers, 160 bytes are eligible for reclamation, while the reachable B–C cycle must remain.

The result

Cycles are not inherently garbage or inherently retained under tracing. Their reachability from roots determines the result in the simplified model.

Explore the model

Interactive lab

Interactive lab · probability

Survival fraction changes live payload

Set n=20 and p=0.25 in the exact binomial model. Predict five survivors on average and variance 3.75, then raise p. Interpret 64 bytes per survivor only as a hypothetical payload model. These independent trials do not trace an object graph or model a real collector.

Survival fraction changes live payloadThese are exact binomial probabilities for independent trials with one fixed success probability. Playback tours the distribution; it does not resample outcomes. Current readings: Mean · np: 5; Variance · np(1−p): 2.5; Total probability: 1. All parameters are adjustable with the labeled controls below.03581000.070.150.220.3Successes kP(X = k)k = 0 · P = 9.77e-4
Exact probabilityHighlighted outcomeMean np
Mean · np
5
Variance · np(1−p)
2.5
Total probability
1
t = 0 s

These are exact binomial probabilities for independent trials with one fixed success probability. Playback tours the distribution; it does not resample outcomes. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forThe number allocated and the number remaining live are different quantities; real reachability adds graph dependencies.

Check your understanding

A cycle is disconnected from every root in a precise strong-edge tracing model with no finalizers. What follows?

Try it yourself

Add one edge from C to D in the worked graph. Which payload is now live, and which object remains eligible under the same assumptions?

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Continue following edges from the existing reachable set, including the D–E cycle.

Compare with the solution

The added edge reaches D and then E, making {A,B,C,D,E} live. Their payload is 128+64+64=256 bytes. Only F remains unreachable, contributing 32 eligible bytes. Removing the direct root to A instead would change the closure again; internal cycles alone are not roots.

Take it with you

  • Tracing computes reachability from runtime roots rather than counting internal owners.
  • Marking, movement, allocator reuse, and operating-system release are distinct steps.
  • Post-collection live paths reveal retention that collection frequency cannot fix.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.