21 · 9 steps
Visualize / 21

Garbage collection.

A program creates objects on the heap. Some it stops using. The GC\'s job is to figure out which ones are no longer reachable from anywhere live, and reclaim their memory — without the program noticing. Watch tri-color mark-and-sweep walk a small object graph, handle a cycle, and free the unreachable bits.


step 1 / 9 · phase idle
IDLERECLAIMED · 0 B ROOT Userid: Awhite Profileid: Bwhite Sessionid: Cwhite Photoid: Dwhite Cookieid: Ewhite OldCacheid: Fwhite Stale Imgid: Gwhite Tmpid: HwhiteTRI-COLOR · WHITE (unvisited) · GRAY (work stack) · BLACK (done) · DEAD (reclaimed)
Heap before GC · 8 objects · 1 root

Eight objects on the heap. Only one is rooted (User, A) — meaning it's reachable from a stack variable, a global, or a register. The rest are connected to each other or floating. The GC's job: find everything reachable from roots; reclaim the rest. We'll walk a classic mark-and-sweep, then mention how generational and concurrent collectors improve on it.

GC root
A reference from outside the heap that's definitely live: stack locals, global variables, CPU registers, thread-local storage. Reachability starts here.
Reachable
Reachable from a root by following references transitively. Everything else is garbage.
Tri-color marking
White = unvisited, gray = visited but children unscanned, black = visited and children scanned. The mark phase moves objects from white → gray → black.

Why ref-counting alone isn\'t enough

Reference counting increments a counter on every assignment, decrements on every "I\'m done." When the count hits 0, free immediately. Simple, predictable. Until you have a cycle: A points to B, B points to A. Neither count ever reaches 0, even when nothing else references either of them. Pure ref-counting languages (Swift, early Python) need cycle detection as a backup, or you have to manually break cycles with weak references.

Generational GC · the killer optimisation

The generational hypothesis: most objects die young. A web request creates short-lived objects; the response gets serialized and they all become garbage in milliseconds. Generational collectors split the heap into a small "young" generation and a larger "old" generation. They collect young aggressively (cheap, finds most garbage) and old rarely (expensive, but most stuff there is long-lived). JVM\'s G1 and ZGC, V8\'s scavenger + Major GC, .NET\'s tiered GC — all use this principle.

Concurrent vs stop-the-world

Old GCs paused the application entirely while collecting. Multi-second pauses on big heaps. Modern GCs (Go\'s GC, JVM\'s ZGC, V8\'s incremental marker) do most of the work concurrently with the application. They still need short stop-the-world phases for safety (e.g. marking roots), but those are measured in single-digit milliseconds even on 100 GB heaps. The catch: concurrent GCs need write barriers — every pointer write costs a few extra instructions to inform the GC. The cost is a few percent of CPU; the payoff is no multi-second pauses.

Go deeper

GC internals · the long version →

Go\'s tricolor concurrent GC, JVM\'s G1 / ZGC / Shenandoah, V8\'s scavenger + Major GC, .NET\'s tiered GC, write barriers in detail.

Open the Codex →
Found this useful?