V8pedia

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:

  1. Scan the roots and the remembered set (old→young pointers) for references into from-space.

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

  3. 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).

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