Skip to content

Garbage collection

V8 (Chrome & Node) uses generational garbage collection with mark-and-sweep as the core idea.

  1. Mark — starting from roots, traverse all reachable objects.
  2. 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.

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.

  • 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.

Browser: DevTools → Memory → trash icon (Collect garbage).

Node:

Terminal window
node --expose-gc app.js
if (global.gc) global.gc();

Never rely on forced GC in production.

const listeners = [];
setInterval(() => {
const data = loadHugePayload();
listeners.push(() => console.log(data.length)); // promoted to old gen, never freed
}, 1000);
let intervalId;
function start() {
intervalId = setInterval(() => {
const data = loadHugePayload();
console.log(data.length);
}, 1000);
}
function stop() {
clearInterval(intervalId);
}