Scavenger (young generation)
The Scavenger is V8's minor garbage collector: it collects the young generation frequently and cheaply. It is a parallel copying collector, and its cost is proportional to the surviving objects — which, by the generational hypothesis, are few. This is why allocating and discarding lots of short-lived objects is so cheap in V8.
::: info Ubiquitous language Scavenge: a minor GC. Cheney's algorithm: semispace copying collection. Promotion: moving a survivor from young to old generation. From/to space: the two halves of new space that swap roles each scavenge. :::
Bump-pointer allocation: why young objects are cheap to create
Before collection, note how young objects are allocated — by simply advancing a pointer in a thread-local linear allocation area:
V8_INLINE bool CanIncrementTop(size_t bytes) const {
return (top_ + bytes) <= limit_;
}
V8_INLINE Address IncrementTop(size_t bytes) {
Address old_top = top_;
top_ += bytes;
return old_top;
}
— src/heap/linear-allocation-area.h#L19-L46
No free-list search, no per-object locking (the buffer is thread-local), no
fragmentation — allocation is a compare and an add. The
HeapAllocator
dispatches to the right space and refills these buffers. Bump allocation is only
possible because the Scavenger keeps young space compact by copying.
Cheney's algorithm: copy the living, abandon the dead
New space is two equal semispaces. Objects are allocated in to-space (which becomes from-space at the start of a scavenge). To collect:
Scan the roots and the remembered set (old→young pointers) for references into from-space.
Copy each live object into the new to-space, leaving a forwarding pointer in its old location so other references update to the new address.
Scan copied objects for more from-space pointers, copying transitively (the "scan finger" sweep that gives Cheney's algorithm its elegance — no separate work stack needed).
When done, everything not copied is dead. Swap the semispaces. Reclaiming the dead is free: the whole from-space is simply reused.
class ScavengerCollector {
public:
// Performs synchronous parallel garbage collection based on semi-space
// copying algorithm.
void CollectGarbage();
};
— src/heap/scavenger.h#L14-L35
The crucial property: cost is O(live), not O(allocated). If 95% of young objects are dead (typical), the Scavenger only touches the 5% that survive. Dead objects cost nothing to reclaim. This is the copying collector's superpower for short-lived garbage.
Promotion: graduating survivors
An object that survives (V8 uses survival across scavenges as the signal) is promoted to the old generation rather than copied within young space:
bool HeapObjectWillBeOld(const Heap* heap, Tagged<HeapObject> object) {
if (!HeapLayout::InYoungGeneration(object)) return true;
if (HeapLayout::InAnyLargeSpace(object)) return true;
if (HeapLayout::IsSelfForwarded(object) &&
BasePage::FromHeapObject(heap->isolate(), object)->will_be_promoted()) {
return true;
}
return false;
}
— src/heap/scavenger.cc#L85-L98
Promotion keeps long-lived objects out of the frequently-scanned young space, so the Scavenger stays cheap over time.
Parallelism and the generational invariant
The scavenge runs across multiple threads (--parallel-scavenge, on by
default), dividing the work of copying and scanning. To find old→young pointers
without scanning all of old space, the Scavenger relies on the remembered set
maintained by the write barrier; and when it promotes an
object that still points into young space, it records that new old→young edge:
if (V8_LIKELY(HeapObjectWillBeOld(heap_, host)) &&
V8_UNLIKELY(!HeapObjectWillBeOld(heap_, object))) {
AddToRememberedSet<OLD_TO_NEW>(heap_, host, slot.address());
}
— src/heap/scavenger.cc#L161-L206
::: tip Performance takeaway for language implementers The Scavenger is a textbook lesson: a copying collector over a small nursery turns "allocate tons of temporaries" from a liability into a non-event, because reclamation cost tracks survivors, not allocations. The bump-allocator + copying nursery pairing is why idiomatic, allocation-heavy JS is viable at all. :::
See also
Overview & generational GC — why young/old split exists.
Write barriers — how old→young pointers are tracked.
Mark-Compact — what collects the objects that get promoted.