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.
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 manuallyUse WeakMap or WeakSet for weak references; they do not prevent garbage collection of the referenced objects.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
