V8 GC: Scavenge (Semi-Space Young Gen) vs Mark-Sweep-Compact (Old Gen)
- Last updated
- Prerequisites:
- Node.js memory basics
- Understanding of GC concepts (young/old generations)
- nodejs
- v8
- garbage-collection
- scavenger
- mark-compact
- memory
Read at your depth
The practical view
V8 (the JS engine in Node.js) divides the heap into generations. The young generation uses a semi-space (from-space/to-space) collector called Scavenger: most objects die young, so it quickly copies surviving objects to the to-space and frees the from-space. Objects surviving multiple scavenges are promoted to the old generation. The old generation is collected with Mark-Sweep-Compact (when needed): mark reachable objects, sweep unreachable ones, and optionally compact to reduce fragmentation. The two generations are why Node memory usage grows in steps and why 'minor GC' (scavenge) is much cheaper than 'major GC'.
The same idea in other frameworks
N/A — V8 runtime concern.N/A — V8 concern.N/A — V8 concern.N/A — V8 concern.Legacy vs modern
Allocation churn vs stable references
Creating short-lived objects per request triggers constant scavenges; reusing/long-lived structures reduce young-gen pressure.
function handle(req) {
const result = { data: req.body.split(',').map(Number) }; // ❌ OLD: new objects per request// ✅ NEW: reuse buffers and avoid per-request temp strings where cheapInterview gotchas
Context
This tests the generational model — interviewers want the semi-space copy vs old-gen mark/sweep/compact mechanics and the frequency/cost asymmetry that defines V8's pause profile.
The mechanical answer
Scavenge is a minor GC on the young generation: a semi-space collector that copies the small set of surviving objects from from-space to to-space (and promotes survivors), then swaps the spaces. Because most objects die young, the survivor set is small, so scavenge is fast (a few ms or less) and frequent (happens as the nursery fills — often many times per second under allocation load). Mark-Compact is a major GC on the old generation: it marks all reachable objects from roots (tracing the whole heap), sweeps the unreachable ones, and optionally compacts (moving objects to defragment). It traverses the entire old heap, so it's much more expensive and much rarer (triggered by old-gen allocation pressure or heap-limit proximity). The asymmetry is why V8 uses a generational design: cheap frequent cleaning for short-lived garbage, expensive rare cleaning for long-lived data.
Trap
The naive answer is 'GC pauses are always bad'. Minor GCs are cheap and frequent; major GCs are expensive and rare — tuning targets the major GCs. Another trap: assuming old-gen objects are never collected — they are, but only by major GC, so long-lived caches grow until a major GC reclaims them. Candidates who describe the two generations and their cost asymmetry show real V8 understanding.
Context
This probes the concurrent/incremental GC design (Orinoco) — interviewers want the mark-bitmap + background threads + write barriers that shift GC work off the main thread.
The mechanical answer
V8's Orinoco GC is incremental and concurrent: marking runs in small increments interleaved with the mutator on the main thread, AND on dedicated helper (background) threads in parallel. The mark-bitmap records reachability; write barriers maintain a remembered set of old→young pointers (the store buffer) so the GC knows which old objects to re-scan when a young object is promoted. Sweeping is concurrent (frees pages on background threads), and compaction is also concurrent when enabled. The result: the main thread only pauses for short, bounded steps (the incremental mark slices and the roots scan), so total pause time is small even for large heaps — at the cost of extra CPU on helper threads and slightly higher total GC work. This is why Node apps with big heaps can still keep sub-50ms pauses.
Trap
The naive answer is 'V8 stops the world for the whole major GC'. Modern V8 does most marking/sweeping concurrently; the pauses are the incremental slices. Another trap: thinking concurrent GC is free — it uses CPU on background threads, which can compete with your workload on multi-tenant machines. Candidates who describe the incremental marking and write barriers show real depth.