Garbage Collection (V8)
How V8 automatically frees memory β generational collection, mark-and-sweep, and why memory leaks still happen.
JavaScript automatically manages memory through garbage collection. V8 uses a generational strategy: a fast Scavenger for short-lived objects and a Mark-Sweep-Compact collector for long-lived objects. Understanding GC helps prevent memory leaks and optimize performance-critical applications.
V8 divides the heap into Young Generation (short-lived objects) and Old Generation (long-lived objects). Most objects die young β 70-90% of allocations are collected in the first GC cycle. This insight drives V8's two-collector strategy.
Uses a semi-space copying algorithm. Memory is split into 'from-space' and 'to-space'. During GC: 1) Trace reachable objects from roots. 2) Copy living objects to to-space. 3) Swap the spaces. Very fast (~1-2ms) but covers small memory.
For long-lived objects that survive 2+ Scavenger cycles. Mark: trace from roots, mark reachable. Sweep: free unmarked memory. Compact: defragment by moving objects together. Slower but handles the full heap.
V8 doesn't stop the world for the entire GC. Mark phase is incremental (interleaved with JS execution). Sweeping runs on background threads concurrently. This keeps GC pauses under 1ms in most cases.
Key Concepts
An object is 'alive' if it's reachable from a root (global, stack, active closures). Unreachable objects are candidates for collection.
Starting points for reachability analysis: global object, current call stack variables, active closures, and registered callbacks.
Small heap region (~1-8MB) for new allocations. Collected frequently by the fast Scavenger algorithm.
Larger heap region for objects that survived 2+ young-gen collections. Collected by the slower Mark-Sweep-Compact.
Mechanism that tracks when old-gen objects point to young-gen objects. Without it, the Scavenger would need to scan the entire heap.
1// Memory lifecycle in JavaScript:2// 1. Allocate β 2. Use β 3. Release (automatic via GC)34let user = { name: "Alice", data: new Array(1e6) };5// β Allocated in Young Generation (V8 heap)67user = null;8// β No more references β eligible for GC9// β V8 will collect it in next Scavenge cycle1011// Common leak patterns:12// 1. Forgotten timers13const id = setInterval(() => {14 doSomething(hugeData); // hugeData can't be GC'd15}, 1000);16// Fix: clearInterval(id) when done1718// 2. Closures retaining large objects19function process() {20 const bigData = loadHugeFile();21 return () => bigData.length; // closure keeps bigData alive!22}2324// 3. Detached DOM nodes25const el = document.createElement("div");26document.body.append(el);27el.remove(); // removed from DOM but still referenced by 'el'28// Fix: el = null; after removal
Memory leaks are the #1 production issue in long-running JavaScript applications (SPAs, Node.js servers). Understanding GC reveals why leaks happen, how to detect them with heap snapshots, and how to write allocation-efficient code.
Common Pitfalls
1Memory Leak from Event Listeners
Your SPA adds scroll listeners for infinite loading. After navigating away, memory usage keeps climbing. The heap snapshot shows thousands of detached DOM trees.
Event listeners from unmounted components still reference DOM nodes and component closures. The GC can't collect them because the listener registration (on window/document) holds a strong reference.
Always remove event listeners in cleanup: React's useEffect return function, or AbortController for addEventListener. Use WeakRef or WeakMap for caches that should be GC-eligible.
Takeaway: The GC can only collect unreachable objects. Event listeners, timers, and global references create 'invisible' roots that keep object trees alive. Always clean up registrations when components unmount.
2GC Pauses Causing Frame Drops
Your canvas-based game or animation stutters every few seconds. DevTools Performance panel shows minor GC pauses of 5-10ms β enough to drop a frame at 60fps.
The game allocates thousands of small objects per frame (particles, vectors, collision results). The young generation fills up quickly, triggering frequent Scavenger runs. Each run pauses JS execution.
Object pooling: pre-allocate a fixed pool of objects and recycle them instead of creating new ones. Avoid allocations in hot paths. Use TypedArrays for numeric data (contiguous memory, no GC overhead).
Takeaway: GC is automatic but not free. In performance-critical code (games, animations, audio processing), minimizing allocations is crucial. Object pooling and TypedArrays bypass GC pressure entirely.