Node.js Memory Leaks Detection — Complete Guide to Debugging Heap Growth
In this tutorial, you will learn about Node.js Memory Leaks Detection. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js memory leaks occur when objects are unintentionally retained by the garbage collector, causing heap growth over time until the process runs out of memory and crashes.
What You'll Learn
By the end of this tutorial, you'll detect memory leaks using heap snapshots, use GC tracing, identify common leak patterns like event listeners and closures, and implement monitoring.
Why Leak Detection Matters
Memory leaks cause production outages, degrade performance through increasing GC pauses, and waste cloud resources. Early detection prevents costly incidents.
Real-World Use
A microservice crashes every 48 hours. Heap snapshot comparison reveals an Express route handler that registers new event listeners on each request without removing them, growing unbounded.
Leak Detection Path
flowchart LR
A[Profiling] --> B[Memory Leaks]
B --> C[PM2]
C --> D[Security]
D --> E[Docker]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Common Leak: Event Listeners
Registering event listeners without removing them is the most common leak source.
const http = require("node:http");
const server = http.createServer((req, res) => {
// BAD: New listener on every request, never removed
process.on("customEvent", () => {
// This listener accumulates with every request
});
// GOOD: Register once, or use once()
res.end("ok");
});
// Inspect listener count:
setInterval(() => {
console.log("Listeners:", process.listenerCount("customEvent"));
}, 1000);
Common Leak: Closure Captures
Closures that capture large objects prevent them from being garbage collected.
function createLeak() {
const largeData = new Array(1000000).fill("leak");
return function() {
// This closure holds reference to largeData
console.log(largeData.length);
};
}
const leaks = [];
setInterval(() => {
leaks.push(createLeak());
console.log(`Leaked ${leaks.length} closures`);
}, 100);
Common Leak: Cache Without Eviction
Unbounded caches grow until memory is exhausted.
const cache = new Map();
async function getUser(id) {
if (cache.has(id)) return cache.get(id);
const user = await fetchUserFromDB(id);
cache.set(id, user);
return user;
}
// FIX: Add size limit and eviction
class BoundedCache {
constructor(maxSize = 1000) {
this.maxSize = maxSize;
this.cache = new Map();
}
get(key) { return this.cache.get(key); }
set(key, value) {
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
Common Leak: Object References in Arrays
Adding objects to persistent arrays without clearing causes growth.
const requestLog = [];
const http = require("node:http");
const server = http.createServer((req, res) => {
requestLog.push({
url: req.url,
timestamp: Date.now(),
headers: req.headers, // Retains large objects
});
res.end("ok");
});
// FIX: Limit array size or use a circular buffer
class CircularBuffer {
constructor(size = 1000) {
this.buffer = new Array(size);
this.size = size;
this.index = 0;
}
add(item) {
this.buffer[this.index] = item;
this.index = (this.index + 1) % this.size;
}
}
Using --trace-gc for GC Events
Enable GC tracing to see garbage collection frequency and duration.
// Run: node --trace-gc app.js
// Output shows GC events with type, duration, and heap sizes
console.log("Run with: node --trace-gc app.js");
console.log("GC types: Scavenge (young), Mark-sweep (old)");
console.log("High GC frequency indicates allocation pressure");
Common Mistakes
1. Ignoring the Event Emitter Default MaxListeners Warning
The warning at 10 listeners per event often signals a listener leak. Investigate before increasing.
2. Assuming WeakRef Solves All Leaks
WeakRef helps but does not prevent leaks. Objects must still become unreachable through normal references.
3. Not Testing Memory Under Load
Some leaks only appear under production load. Test with sustained traffic for 30+ minutes.
4. Forgetting Global Variables
Data attached to the global object (global, globalThis) is never garbage collected.
5. Confusing RSS with Heap Usage
RSS includes compiled code, stack, and external allocations. Use heap snapshots for JavaScript objects.
Practice Questions
1. What is the most common cause of memory leaks in Node.js?
Event listeners registered without removal. Each registration keeps a reference to the listener function.
2. How do you compare two heap snapshots?
Take a snapshot, perform actions, take another snapshot. Load both in Chrome DevToolsk "DevTools" >}} and compare objects.
3. What does the --trace-gc flag do?
Prints garbage collection events with timestamps, type, and heap statistics to stderr.
4. How does a closure cause a memory leak?
A closure retains references to variables in its outer scope. If the closure is long-lived, those variables cannot be GC'd.
5. Challenge: Create a memory-leaking server and then fix it with proper cleanup.
// Leaking version
app.get("/leak", (req, res) => { process.on("data", () => {}); res.end("ok"); });
// Fixed version
app.get("/fixed", (req, res) => { process.once("data", () => {}); res.end("ok"); });
FAQ
Mini Project: Memory Leak Detector
Build a monitoring script that warns about abnormal heap growth.
const v8 = require("node:v8");
const fs = require("node:fs");
class LeakDetector {
constructor(thresholdMB = 50) {
this.thresholdBytes = thresholdMB * 1024 * 1024;
this.lastHeap = null;
}
check() {
const stats = v8.getHeapStatistics();
const current = stats.used_heap_size;
if (this.lastHeap !== null) {
const growth = current - this.lastHeap;
if (growth > this.thresholdBytes) {
console.warn(`Potential leak: ${(growth / 1024 / 1024).toFixed(1)}MB growth`);
const snapshot = v8.getHeapSnapshot();
snapshot.pipe(fs.createWriteStream(`/tmp/heap-${Date.now()}.heapsnapshot`));
}
}
this.lastHeap = current;
}
start(intervalMs = 30000) {
setInterval(() => this.check(), intervalMs);
}
}
What's Next
Node.js PM2 Node.js Docker Node.js Security
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro