Mastering Node.js Garbage Collection: Detect and Prevent Memory Leaks
This article explains how Node.js relies on the V8 engine for automatic memory management, details the garbage‑collection mechanisms (Scavenge, Mark‑Sweep, Mark‑Compact), shows practical code for monitoring and forcing GC, illustrates common memory‑leak patterns, and recommends tools for diagnosing and fixing leaks.
Quick Navigation
GC in Node.js
GC practice and memory‑management
V8 GC mechanism
Memory leaks
Memory‑inspection tools
GC in Node.js
Node.js runs on the Chrome V8 engine, so its garbage collection is essentially V8's GC. Like Java, memory is managed automatically by the virtual machine.
However, developers still need to understand how the VM uses memory because programming mistakes can cause serious memory leaks.
GC Practice in Node.js
Let's see how garbage collection works in Node.js with a demo.
Leak Detection
Node.js provides process.memoryUsage() to inspect current memory usage (bytes):
rss : resident set size – total memory occupied by the process (code, stack, heap).
heapTotal : total heap memory allocated.
heapUsed : heap memory currently used (primary metric for leak detection).
external : memory used by V8 internal C++ objects.
/**
* Format bytes to MB string
*/
const format = function (bytes) {
return (bytes / 1024 / 1024).toFixed(2) + ' MB';
};
/**
* Print memory usage
*/
const print = function () {
const memoryUsage = process.memoryUsage();
console.log(JSON.stringify({
rss: format(memoryUsage.rss),
heapTotal: format(memoryUsage.heapTotal),
heapUsed: format(memoryUsage.heapUsed),
external: format(memoryUsage.external)
}));
};Leak Example
The following code creates a Fruit object stored on the heap. The banana instance allocates a huge array, causing a large increase in heapUsed.
// example.js
function Quantity(num) {
if (num) {
return new Array(num * 1024 * 1024);
}
return num;
}
function Fruit(name, quantity) {
this.name = name;
this.quantity = new Quantity(quantity);
}
let apple = new Fruit('apple');
print();
let banana = new Fruit('banana', 20);
print();Running the script shows apple using only ~4.21 MB, while banana pushes heapUsed to ~164 MB.
{"rss":"19.94 MB","heapTotal":"6.83 MB","heapUsed":"4.21 MB","external":"0.01 MB"}
{"rss":"180.04 MB","heapTotal":"166.84 MB","heapUsed":"164.24 MB","external":"0.01 MB"}Because the root set still references banana, the memory cannot be reclaimed until the next GC cycle.
Manual GC
Assigning banana = null and invoking global.gc() (enabled with --expose-gc) releases the memory, reducing heapUsed from 164 MB to ~3.97 MB.
$ node --expose-gc example.js
{"rss":"19.95 MB","heapTotal":"6.83 MB","heapUsed":"4.21 MB","external":"0.01 MB"}
{"rss":"180.05 MB","heapTotal":"166.84 MB","heapUsed":"164.24 MB","external":"0.01 MB"}
{"rss":"52.48 MB","heapTotal":"9.33 MB","heapUsed":"3.97 MB","external":"0.01 MB"}After GC, the banana node disappears from the heap diagram.
V8 Garbage‑Collection Mechanism
GC reclaims objects that are no longer reachable from the root set (global objects, local variables, etc.).
V8 Heap Size Limits
On 64‑bit machines V8 caps the heap at ~1.4 GB; on 32‑bit machines at ~0.7 GB. Exceeding these limits causes the process to exit.
Overflow example
// overflow.js
const format = function (bytes) {
return (bytes / 1024 / 1024).toFixed(2) + ' MB';
};
const print = function () {
const memoryUsage = process.memoryUsage();
console.log(`heapTotal: ${format(memoryUsage.heapTotal)}, heapUsed: ${format(memoryUsage.heapUsed)}`);
};
const total = [];
setInterval(function () {
total.push(new Array(20 * 1024 * 1024)); // large allocation
print();
}, 1000);Each interval adds ~160 MB to the heap; once the limit is reached, allocation fails and the process crashes.
Young and Old Generations
Most objects die quickly (young generation, ~1‑8 MB). V8 uses the Scavenge algorithm (a copying collector) for fast reclamation. Surviving objects are promoted to the old generation, which uses Mark‑Sweep and Mark‑Compact algorithms.
Young Generation (Scavenge)
Scavenge splits the space into two equal halves (from‑space and to‑space). Live objects are copied to to‑space (or promoted), and the spaces are swapped.
Old Generation
Objects that survive multiple collections are kept here. Mark‑Sweep first marks live objects, then clears dead ones. This can cause fragmentation, which Mark‑Compact resolves by moving live objects together.
V8 GC Summary
V8 employs Scavenge for the young generation, Mark‑Sweep and Mark‑Compact for the old generation, and incremental marking to reduce pause times. GC pauses affect the JavaScript thread, especially when many objects survive in the old space.
Memory Leaks
A memory leak occurs when allocated heap memory cannot be released, leading to performance degradation or crashes.
Global Variables
Undeclared variables or properties attached to global persist for the lifetime of the process unless explicitly deleted or set to null / undefined.
Closures
Closures retain references to variables in their outer scope. If a closure is never released, the captured variables also stay in memory, causing leaks.
Example from Meteor:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing) {
console.log("hi");
}
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () { console.log(someMessage); }
};
};
setInterval(replaceThing, 1000);Each call creates a new object while the previous one remains referenced via the closure chain.
Cache Misuse
Storing large amounts of data in memory (e.g., a Map of user tokens) can cause leaks, especially in multi‑process deployments where each process holds its own copy.
const memoryStore = new Map();
exports.getUserToken = function (key) {
const token = memoryStore.get(key);
if (token && Date.now() - token.now > 2 * 60) {
return token;
}
const dbToken = db.get(key);
memoryStore.set(key, { now: Date.now(), val: dbToken });
return token;
};Module Private Variables
Node wraps each module in a function, creating a closure that keeps module‑level variables alive for the process lifetime. Load modules once and cache the exported object.
Repeated Event Listeners
Adding too many listeners to an EventEmitter triggers a MaxListenersExceededWarning, indicating a potential leak. Use emitter.setMaxListeners() judiciously.
(node:23992) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 connect listeners added. Use emitter.setMaxListeners() to increase limitOther Tips
Always clear intervals with clearInterval, avoid unnecessary array creations (prefer forEach over map when not needed), and be mindful of functions that allocate large temporary structures.
console.log(setInterval(function(){}, 1000)); // returns an id
[1,2,3].filter(item => item % 2 === 0); // creates a new array
[1,2,3].map(item => item % 2 === 0); // creates a new array of booleansMemory‑Inspection Tools
node-heapdump – dumps V8 heap snapshots.
node-profiler – Alinode’s tool for capturing heap snapshots.
Easy-Monitor – lightweight performance monitoring for Node.js.
Node.js‑Troubleshooting‑Guide – comprehensive guide for diagnosing and optimizing Node.js applications.
alinode – performance platform offering monitoring, security alerts, and optimization services.
Further Reading
Node.js Garbage Collection Explained
A tour of V8: Garbage Collection (Chinese)
Memory Management Reference
深入浅出 Node.js
如何分析 Node.js 中的内存泄漏
公众号 “Nodejs技术栈”
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Node Underground
No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.
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.
