V8 in browser and Node
Both Chrome and Node.js run JavaScript on V8. The language semantics and GC model are the same; the host environment differs.
flowchart TB
subgraph v8 [V8 Engine]
parser[Parser / Ignition]
opt[Optimizing compiler]
heap[Heap + GC]
end
subgraph browser [Browser host]
dom[DOM / Web APIs]
perf[performance.memory]
devtools[Chrome DevTools]
end
subgraph node [Node host]
libuv[libuv event loop]
proc[process.memoryUsage]
inspect[--inspect / v8 module]
end
v8 --> browser
v8 --> node
Shared leak patterns
Section titled “Shared leak patterns”These behave similarly in both environments:
- Global/module-scope caches
- Closures capturing large data
- Forgotten timers (
setInterval) - Unremoved event listeners (
EventEmitterin Node)
Browser-specific roots
Section titled “Browser-specific roots”- DOM nodes and event listeners
windowglobalsrequestAnimationFramecallbacks- Service Workers / IndexedDB (persistent by design — not leaks, but retention)
Node-specific roots
Section titled “Node-specific roots”- Active handles (timers, TCP sockets, file descriptors)
- Event loop keeps process alive while handles exist
- Module-level singletons (common in servers)
- Buffers and native addons (show up in
externalmemory)
Measuring memory
Section titled “Measuring memory”| Runtime | API | Notes |
|---|---|---|
| Browser | performance.memory.usedJSHeapSize |
Chromium only; approximate |
| Node | process.memoryUsage() |
rss, heapUsed, heapTotal, external |
Broken pattern (Node)
Section titled “Broken pattern (Node)”// server.js — module scope lives for process lifetimeconst sessions = new Map();
app.use((req, res, next) => { sessions.set(req.sessionId, req.body); // never deleted next();});Fixed pattern
Section titled “Fixed pattern”const sessions = new Map();const TTL_MS = 30 * 60 * 1000;
function setSession(id, data) { sessions.set(id, { data, at: Date.now() });}
setInterval(() => { const now = Date.now(); for (const [id, s] of sessions) { if (now - s.at > TTL_MS) sessions.delete(id); }}, 60_000);