Garbage collection
V8 (Chrome & Node) uses generational garbage collection with mark-and-sweep as the core idea.
Mark-and-sweep (simplified)
Section titled “Mark-and-sweep (simplified)”- Mark — starting from roots, traverse all reachable objects.
- Sweep — reclaim memory for unmarked objects.
sequenceDiagram participant App participant GC as V8 GC participant Heap App->>Heap: Allocate objects Note over Heap: Some become unreachable GC->>Heap: Mark from roots GC->>Heap: Sweep unmarked blocks Heap-->>App: Memory reused
If an object is still marked reachable — even accidentally — it survives every GC cycle. That’s a leak.
Generational hypothesis
Section titled “Generational hypothesis”Most objects die young. V8 splits the heap:
| Generation | Typical contents | Collection frequency |
|---|---|---|
| Young (nursery) | Short-lived temporaries | Frequent, fast (Scavenge) |
| Old | Long-lived objects | Less frequent, slower (Mark-Compact) |
Objects that survive several young GC cycles get promoted to old generation. Leaks usually end up here — causing steady old-gen growth.
Major vs minor GC
Section titled “Major vs minor GC”- Minor GC — cleans young generation; you may see small sawtooth dips in memory graphs.
- Major GC — cleans old generation; larger pauses; can free big chunks if references were dropped.
Forcing GC (debugging only)
Section titled “Forcing GC (debugging only)”Browser: DevTools → Memory → trash icon (Collect garbage).
Node:
node --expose-gc app.jsif (global.gc) global.gc();Never rely on forced GC in production.
Broken pattern
Section titled “Broken pattern”const listeners = [];
setInterval(() => { const data = loadHugePayload(); listeners.push(() => console.log(data.length)); // promoted to old gen, never freed}, 1000);Fixed pattern
Section titled “Fixed pattern”let intervalId;
function start() { intervalId = setInterval(() => { const data = loadHugePayload(); console.log(data.length); }, 1000);}
function stop() { clearInterval(intervalId);}