Fundamentals 21 min read

Understanding V8’s Memory Management and Garbage Collection Strategies

This article explains how the V8 JavaScript engine manages memory through stack and heap allocation, describes its generational garbage‑collection architecture—including New Space, Old Space, and various GC algorithms—and offers practical optimization techniques to reduce pause times and improve performance.

ELab Team
ELab Team
ELab Team
Understanding V8’s Memory Management and Garbage Collection Strategies

Memory Management

Memory is one of the most important components of a computer, acting as the bridge between the CPU and data. Programs run in memory, so memory performance greatly influences overall system speed. RAM is the most critical part of the semiconductor storage unit (ROM + RAM + Cache).

The typical memory lifecycle is: allocate size → use (read/write) → release when no longer needed.

When running JavaScript, memory usage includes both heap and stack.

Stack

Small, contiguous array structure allocated automatically by the system with a relatively fixed size and released automatically, following a LIFO (last‑in‑first‑out) rule. It stores local variables and manages function calls.

Primitive values are stored directly on the stack, while complex types store a reference (pointer) on the stack and the actual object in the heap.

Each function call creates a call stack; the interpreter adds a stack frame for the function and executes it. When the function finishes, its stack frame is destroyed. A stack frame typically contains:

Function return address and arguments

Temporary variables (local variables and compiler‑generated temporaries)

Function call context

(Function call stack order)

Why do most high‑level languages use a stack for function calls?

Because the caller’s lifetime always exceeds the callee’s (the callee finishes first), and the callee’s resources are allocated after the caller’s and released before the caller’s.

Heap

Why is a heap needed in addition to a stack?

Stack allocation is fast but limited in size; large or many allocations can cause stack overflow. The heap provides a flexible area for large or variable‑size objects.

Reference‑type data are stored in the heap because they are large and have unpredictable size. The stack holds a pointer to the heap object; the interpreter follows the pointer to access the actual data.

V8’s heap is divided into several regions:

New space (young generation) – composed of two semi‑spaces (from‑space and to‑space) whose sizes are controlled by --min_semi_space_size and --max_semi_space_size.

Old space (old generation) – stores objects that survive multiple GC cycles; size controlled by --initial_old_space_size and --max_old_space_size.

Old pointer space – objects that contain pointers to other objects.

Old data space – objects that contain only raw data (e.g., strings).

Large object space – stores objects larger than the other spaces; these are not moved by GC.

Code space – holds JIT‑compiled code; the only executable region.

Cell space, Property cell space, Map space, Stack (as a region within the heap).

Garbage Collection

What is GC?

GC (Garbage Collection) reclaims memory that is no longer reachable. Unlike languages such as C/C++, JavaScript developers do not manually allocate or free memory; the engine handles it, improving productivity but adding a dependency on the engine’s implementation.

ECMAScript does not specify GC; it is entirely provided by the underlying engine.

GC traverses the heap, finds objects that are not reachable from GC roots, and frees them. The efficiency of the GC algorithm directly impacts engine performance.

How to identify inactive objects?

Reference‑counting (not used by V8 because it cannot handle cycles).

Reachability analysis – V8 starts from GC roots (global objects, DOM nodes, etc.) and marks all reachable objects as live; unreachable objects are considered garbage.

Generational Hypothesis

Most objects die young (e.g., local variables), while long‑living objects include globals, DOM nodes, and APIs. V8 therefore uses two GC strategies:

Minor GC (Scavenger) for the young generation, using copying collection.

Major GC (Mark‑Sweep & Mark‑Compact) for the old generation.

Scavenger Algorithm

Used in the young generation; it copies live objects between the two semi‑spaces. Objects that survive several Scavenger cycles or cause the to‑space to exceed 25 % of its size are promoted to the old generation.

Mark‑Sweep & Mark‑Compact

Applied to the old generation. Mark‑Sweep identifies live objects and frees the rest, leaving fragmented free space. Mark‑Compact then moves live objects together to eliminate fragmentation.

Optimization Strategies

Because JavaScript runs on the main thread, GC pauses (Stop‑The‑World) can cause noticeable jank in animations and user interactions.

Parallel Collection

Multiple auxiliary threads perform GC work in parallel with the main thread, reducing total pause time, especially for the young generation.

Incremental Collection

Divides the marking phase into small chunks that run between JavaScript tasks, similar to React Fiber.

Requires the ability to pause/resume marking and to handle objects that change while marking is paused.

Orinoco uses a three‑color marking scheme to support incremental pauses without losing progress.

Three‑Color Marking

All objects start as white.

Traverse from GC roots, marking reachable objects gray and adding them to a work queue.

Process gray objects: mark their children gray, then mark the object black.

When the gray queue is empty, white objects are garbage and can be reclaimed.

Write Barrier

If a black or gray object stops being referenced, it will be reclaimed in the next GC cycle.

If a black object acquires a reference to a white object, the white object is immediately turned gray (the “strong tri‑color invariant”).

Concurrent Collection

While the main thread runs JavaScript, auxiliary threads concurrently mark objects; the main thread later performs the sweeping phase in parallel, further reducing pause times.

Summary

Excerpt from the V8 blog: Since its inception, V8’s garbage collector has added parallel, incremental, and concurrent techniques, moving a large amount of work to background tasks. This dramatically improves pause times, latency, and page load speed, making animations, scrolling, and user interaction smoother. Parallel Scavenger reduces young‑generation GC time by roughly 20‑50 %, and idle‑time GC can shrink the JavaScript heap by up to 45 % in Gmail. Concurrent marking and sweeping cut pause times for heavy WebGL games by up to 50 %.

Code Suggestions

How to avoid memory leaks
function foo() {
    a = 1; // equivalent to window.a = 1
}
const a = [];
const foo = () => {
    for (let i = 0; i < 1000; i++) {
        a.push(i);
    }
};
window.setInterval(foo, 1000);
function foo() {
    let a = 123;
    return function() {
        return a;
    };
}
const bar = foo();
console.log(bar()); // closure keeps a alive
const elements = {
    button: document.getElementById('button')
};
function removeButton() {
    document.body.removeChild(document.getElementById('button'));
}
// removeChild clears the DOM node, but the reference in `elements` must be cleared manually

Use WeakMap or WeakSet for weak references; they do not prevent garbage collection of the referenced objects.

JavaScriptmemory managementGarbage CollectionV8engine internals
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.