Memory Management in JavaScript: Garbage Collection, Heap, and Leak Detection

Memory Management in JavaScript: Garbage Collection, Heap, and Leak Detection

JavaScript manages memory automatically, but “automatic” does not mean “leak-proof.” Forgotten event listeners, closures holding large arrays, detached DOM nodes, and Map entries that accumulate silently — these are real production problems that cause SPAs to consume gigabytes over hours of use. This guide explains how V8’s garbage collector works and how to find and fix memory leaks before they reach production.

TL;DR: V8 GC uses generational collection: most objects die young (Minor GC, fast). Long-lived objects promoted to Old Generation (Major GC, slower). Memory leaks = objects the GC cannot collect because something still holds a reference. Find them with Chrome DevTools heap snapshots. Prevent them with WeakMap, WeakRef, and proper cleanup.

How V8 garbage collection works

// V8 memory layout:
// New Space (Young Generation): ~1-8MB, most objects start here
//   - Scavenger GC (Minor GC): runs frequently (~ms)
//   - Objects that survive 2 scavenges are promoted to Old Space
// Old Space (Old Generation): larger, long-lived objects
//   - Mark-Sweep-Compact (Major GC): less frequent, can take 100ms+
//   - Stop-the-world: JS execution pauses during full GC

// Key insight:
// Most objects die young (short-lived temp variables, small objects)
// New Space is small → Minor GC is fast
// Only long-lived objects reach Old Space → Major GC less frequent

// An object is collectable when:
// No more references pointing to it from roots (global, call stack, closures)
// Reachability: GC traces from roots, unreachable = collectable

// An object LEAKS when:
// Something still references it that you forgot about
// Most common: event listener, closure, Map/Set entry, timer callback

The 4 most common memory leak patterns

// Leak 1: Forgotten event listeners
class SearchComponent {
  init() {
    document.addEventListener('keydown', this.handleKey); // Added
    // Component destroyed, but listener never removed
    // document still holds reference to handleKey which holds 'this'
  }
  destroy() {
    document.removeEventListener('keydown', this.handleKey); // Must remove!
  }
}

// Leak 2: Closure over large data
function processData() {
  const bigArray = new Array(1_000_000).fill(0); // 8MB
  return function getBigArrayLength() {
    return bigArray.length; // bigArray kept alive by closure!
  };
}
// Fix: only capture what you need
function processData() {
  const length = new Array(1_000_000).fill(0).length; // Compute, then release
  return () => length; // Only captures the number, not the array
}

// Leak 3: Growing Map/Set without cleanup
const cache = new Map();
setInterval(() => {
  const key = generateKey();
  cache.set(key, expensiveCompute()); // Grows forever!
}, 100);
// Fix: bounded cache (LRU) or use WeakMap (GC-friendly)

// Leak 4: Detached DOM nodes
const detached = document.getElementById('modal');
document.body.removeChild(detached);
const store = { modal: detached }; // Still referenced! GC cannot collect.

Finding leaks with Chrome DevTools

// Step 1: Open DevTools → Memory tab
// Step 2: Take baseline heap snapshot (before the action)
// Step 3: Perform the action suspected of leaking
// Step 4: Force GC (trash can icon)
// Step 5: Take second heap snapshot
// Step 6: Compare snapshots — select "Comparison" view

// What to look for:
// Objects with positive "Delta" (more exist after action)
// Large "Shallow size" increases (direct memory of object)
// Large "Retained size" increases (object + everything it keeps alive)

// Detecting leaks with Performance Monitor:
// DevTools → ... menu → Performance Monitor
// Watch: JS heap size over time
// Normal: heap grows and drops (GC runs)
// Leak: heap grows monotonically without dropping

// Node.js: use --inspect flag + Chrome DevTools remotely
// node --inspect server.js
// Open chrome://inspect in Chrome

WeakMap and WeakRef for leak-proof code

// WeakMap: keys are weakly referenced — GC can collect the key object
// When key is collected, the WeakMap entry disappears automatically

// Attach metadata to DOM nodes without preventing GC
const domMetadata = new WeakMap();
const element = document.getElementById('chart');
domMetadata.set(element, { initialized: true, data: [...] });

// When element is removed from DOM and no other reference:
// GC collects element, domMetadata entry automatically removed
// Map: element would be kept alive by Map key reference forever

// WeakRef: hold reference that does NOT prevent GC
let expensiveObject = { data: new Array(1_000_000) };
const ref = new WeakRef(expensiveObject);
expensiveObject = null; // Remove strong reference

// Later:
const obj = ref.deref(); // May be undefined if GC collected it
if (obj) use(obj);       // Always check!

Memory management checklist

  • ✅ Remove event listeners on component/page cleanup
  • ✅ Clear timers: clearTimeout, clearInterval on unmount
  • ✅ Use WeakMap instead of Map for DOM-keyed metadata
  • ✅ Limit cache size — LRU eviction or TTL expiry
  • ✅ Monitor JS heap in Chrome Performance Monitor
  • ✅ Heap snapshot comparison to find specific leaking objects
  • ❌ Never store DOM references in long-lived data structures without WeakRef
  • ❌ Never use closures that capture more data than needed

JavaScript memory management connects to the WeakMap and WeakRef deep dive — this post covers the problem, that post covers the solution in depth. The V8 JIT and memory interaction is covered in the V8 JIT optimization guide. External reference: Firefox Memory tool documentation.

Recommended Reading

Designing Data-Intensive Applications — The essential book every senior developer needs. Covers distributed systems, databases, and production architecture.

The Pragmatic Programmer — Timeless engineering wisdom for writing better, more maintainable code at any level.

Affiliate links. We earn a small commission at no extra cost to you.

Free Weekly Newsletter

🚀 Don’t Miss the Next Cheat Code

Join 1,000+ senior developers getting expert-level JS, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.

✓ No spam✓ Unsubscribe anytime✓ Expert-level only

Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply