Part 8 · 1 chapters · ~8 min

Garbage Collectors

Reachability and roots, reference counting versus tracing, mark-sweep, mark-compact and copying collectors, tri-colour marking, generational collection and write barriers, incremental and concurrent collectors, pause time versus throughput, real collectors (V8 Orinoco, Go, G1 and ZGC), and writing a mark-sweep collector.

9

A mark-sweep collector in TypeScript

code
type Obj = { marked: boolean; refs: Obj[]; size: number };
class Heap {
  objects: Obj[] = []; roots = new Set<Obj>(); bytes = 0;
  alloc(size: number, refs: Obj[] = []) {
    if (this.bytes + size > 1_000_000) this.collect();
    const o = { marked: false, refs, size }; this.objects.push(o); this.bytes += size; return o;
  }
  collect() {
    const stack = [...this.roots];                            // mark: iterative, no recursion limit
    while (stack.length) { const o = stack.pop()!; if (o.marked) continue; o.marked = true; stack.push(...o.refs); }
    const live = this.objects.filter(o => o.marked);          // sweep
    this.bytes = live.reduce((s, o) => s + o.size, 0);
    for (const o of live) o.marked = false;
    this.objects = live;
  }
}

Write barriers: a generational collector scans only the young space, so it must know about old objects pointing at young ones; every pointer store runs a small barrier that records such references. Concurrent collectors (Go, ZGC) use barriers so marking can run while the program mutates the heap, keeping pauses well under a millisecond.

MARK-SWEEP AND GENERATIONS
find what is reachable, free the rest
rootsstack slots, globals, registersmarktrace reachable objectssweepfree unmarked objectsyoung generationmost objects die youngold generationsurvivors promoted
swipe the figure sideways, or tap expand for full screen
1/4
roots
Everything the program can still reach starts from roots: local variables on the stack, globals and registers.
start from rootsstack, globals