Node.js Memory Leak Debugging - Complete Guide
Published: September 25, 2024 | Reading time: 23 minutes
Memory Leak Overview
Memory leaks in Node.js can cause performance degradation and application crashes:
Memory Leak Impact
# Memory Leak Impact
- Performance degradation
- Application crashes
- Resource exhaustion
- System instability
- Poor user experience
- Increased costs
- Debugging complexity
Common Memory Leak Types
Memory Leak Categories
Common Memory Leak Types
# Common Memory Leak Types
# 1. Event Listener Leaks
const EventEmitter = require('events');
const emitter = new EventEmitter();
// Memory leak - listeners not removed
function addListener() {
emitter.on('data', (data) => {
console.log(data);
});
}
// Fix - remove listeners
function addListenerFixed() {
const handler = (data) => {
console.log(data);
};
emitter.on('data', handler);
// Remove listener when done
setTimeout(() => {
emitter.removeListener('data', handler);
}, 1000);
}
# 2. Closure Leaks
function createClosure() {
const largeArray = new Array(1000000).fill('data');
return function() {
// Closure keeps reference to largeArray
return largeArray.length;
};
}
// Fix - avoid unnecessary closures
function createClosureFixed() {
return function() {
// No closure over large data
return 1000000;
};
}
# 3. Timer Leaks
let intervalId;
function startTimer() {
intervalId = setInterval(() => {
console.log('Timer running');
}, 1000);
}
// Memory leak - timer not cleared
// Fix - clear timer
function stopTimer() {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
}
# 4. DOM Reference Leaks (in browser context)
let elements = [];
function addElement() {
const element = document.createElement('div');
elements.push(element);
document.body.appendChild(element);
}
// Fix - remove references
function removeElements() {
elements.forEach(element => {
document.body.removeChild(element);
});
elements = [];
}
# 5. Cache Leaks
const cache = new Map();
function addToCache(key, value) {
cache.set(key, value);
// Memory leak - cache never cleared
}
// Fix - implement cache expiration
function addToCacheFixed(key, value, ttl = 60000) {
cache.set(key, {
value,
expires: Date.now() + ttl
});
// Clean expired entries
setTimeout(() => {
cache.delete(key);
}, ttl);
}
# 6. Stream Leaks
const fs = require('fs');
function readFile() {
const stream = fs.createReadStream('large-file.txt');
// Memory leak - stream not properly closed
stream.on('data', (chunk) => {
console.log(chunk);
});
}
// Fix - properly close streams
function readFileFixed() {
const stream = fs.createReadStream('large-file.txt');
stream.on('data', (chunk) => {
console.log(chunk);
});
stream.on('end', () => {
stream.destroy();
});
stream.on('error', (err) => {
stream.destroy();
console.error(err);
});
}
# 7. Database Connection Leaks
const mysql = require('mysql2');
let connection;
function connectToDatabase() {
connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
// Memory leak - connection not closed
}
// Fix - properly close connections
function connectToDatabaseFixed() {
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
// Close connection when done
connection.end();
}
# 8. Circular Reference Leaks
let obj1 = {};
let obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
// Memory leak - circular reference
// Fix - break circular references
function breakCircularReference() {
obj1.ref = null;
obj2.ref = null;
}
# 9. Global Variable Leaks
let globalData = [];
function addGlobalData() {
globalData.push(new Array(10000).fill('data'));
// Memory leak - global data accumulates
}
// Fix - limit global data
function addGlobalDataFixed() {
if (globalData.length > 100) {
globalData.shift(); // Remove oldest data
}
globalData.push(new Array(10000).fill('data'));
}
# 10. Promise Leaks
let promises = [];
function createPromise() {
const promise = new Promise((resolve) => {
setTimeout(resolve, 1000);
});
promises.push(promise);
// Memory leak - promises accumulate
}
// Fix - limit promise storage
function createPromiseFixed() {
const promise = new Promise((resolve) => {
setTimeout(resolve, 1000);
});
if (promises.length > 1000) {
promises.shift();
}
promises.push(promise);
}
Memory Profiling Tools
Debugging and Profiling
Memory Profiling Tools
# Memory Profiling Tools
# 1. Node.js Built-in Profiler
# Start with heap profiling
node --inspect app.js
# Generate heap snapshot
node --inspect --inspect-brk app.js
# 2. Chrome DevTools
# Connect to Node.js process
# chrome://inspect
# Click "Open dedicated DevTools for Node"
# 3. Clinic.js
npm install -g clinic
clinic doctor -- node app.js
clinic flame -- node app.js
clinic bubbleprof -- node app.js
# 4. 0x
npm install -g 0x
0x app.js
# 5. Node.js Memory Usage
const v8 = require('v8');
function logMemoryUsage() {
const memUsage = process.memoryUsage();
const heapStats = v8.getHeapStatistics();
console.log('Memory Usage:', {
rss: Math.round(memUsage.rss / 1024 / 1024) + ' MB',
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024) + ' MB',
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + ' MB',
external: Math.round(memUsage.external / 1024 / 1024) + ' MB'
});
console.log('Heap Statistics:', {
totalHeapSize: Math.round(heapStats.total_heap_size / 1024 / 1024) + ' MB',
usedHeapSize: Math.round(heapStats.used_heap_size / 1024 / 1024) + ' MB',
heapSizeLimit: Math.round(heapStats.heap_size_limit / 1024 / 1024) + ' MB'
});
}
# 6. Heap Snapshot Analysis
const v8 = require('v8');
function takeHeapSnapshot() {
const snapshot = v8.getHeapSnapshot();
const filename = `heap-${Date.now()}.heapsnapshot`;
const fileStream = require('fs').createWriteStream(filename);
snapshot.pipe(fileStream);
console.log(`Heap snapshot saved to ${filename}`);
}
# 7. Memory Monitoring Script
const fs = require('fs');
const path = require('path');
class MemoryMonitor {
constructor(interval = 5000) {
this.interval = interval;
this.monitoring = false;
this.data = [];
}
start() {
this.monitoring = true;
this.monitor();
}
stop() {
this.monitoring = false;
}
monitor() {
if (!this.monitoring) return;
const memUsage = process.memoryUsage();
const timestamp = new Date().toISOString();
this.data.push({
timestamp,
rss: memUsage.rss,
heapTotal: memUsage.heapTotal,
heapUsed: memUsage.heapUsed,
external: memUsage.external
});
console.log(`Memory: ${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`);
setTimeout(() => this.monitor(), this.interval);
}
saveReport() {
const filename = `memory-report-${Date.now()}.json`;
fs.writeFileSync(filename, JSON.stringify(this.data, null, 2));
console.log(`Memory report saved to ${filename}`);
}
}
# 8. Leak Detection Script
class LeakDetector {
constructor() {
this.baseline = null;
this.threshold = 50 * 1024 * 1024; // 50MB
}
setBaseline() {
this.baseline = process.memoryUsage();
console.log('Baseline memory set');
}
checkForLeak() {
if (!this.baseline) {
console.log('No baseline set');
return;
}
const current = process.memoryUsage();
const increase = current.heapUsed - this.baseline.heapUsed;
if (increase > this.threshold) {
console.log(`Potential memory leak detected: ${Math.round(increase / 1024 / 1024)} MB increase`);
return true;
}
return false;
}
}
# 9. Garbage Collection Monitoring
const v8 = require('v8');
function monitorGC() {
const gc = require('gc-stats')();
gc.on('stats', (stats) => {
console.log('GC Stats:', {
pause: stats.pause,
pauseMS: stats.pauseMS,
gctype: stats.gctype,
before: Math.round(stats.before / 1024 / 1024) + ' MB',
after: Math.round(stats.after / 1024 / 1024) + ' MB'
});
});
}
# 10. Memory Leak Test
function testMemoryLeak() {
const leakDetector = new LeakDetector();
leakDetector.setBaseline();
// Simulate memory leak
const leaks = [];
for (let i = 0; i < 1000; i++) {
leaks.push(new Array(10000).fill('leak'));
}
// Check for leak
if (leakDetector.checkForLeak()) {
console.log('Memory leak confirmed');
}
// Clean up
leaks.length = 0;
global.gc && global.gc();
setTimeout(() => {
if (!leakDetector.checkForLeak()) {
console.log('Memory leak resolved');
}
}, 1000);
}
Memory Optimization Strategies
Prevention and Optimization
Prevention Strategies
- Remove event listeners
- Clear timers and intervals
- Close streams and connections
- Limit cache sizes
- Avoid circular references
- Use weak references
- Implement cleanup routines
Optimization Techniques
- Object pooling
- Lazy loading
- Memory-efficient data structures
- Stream processing
- Garbage collection tuning
- Memory monitoring
- Regular profiling
Production Monitoring
Real-time Memory Monitoring
Production Monitoring
# Production Memory Monitoring
# 1. PM2 Memory Monitoring
pm2 start app.js --name "myapp" --max-memory-restart 100M
pm2 monit
# 2. New Relic Memory Monitoring
const newrelic = require('newrelic');
function logMemoryMetrics() {
const memUsage = process.memoryUsage();
newrelic.recordMetric('Custom/Memory/RSS', memUsage.rss);
newrelic.recordMetric('Custom/Memory/HeapUsed', memUsage.heapUsed);
newrelic.recordMetric('Custom/Memory/HeapTotal', memUsage.heapTotal);
}
# 3. DataDog Memory Monitoring
const StatsD = require('node-statsd');
const client = new StatsD();
function logMemoryToDataDog() {
const memUsage = process.memoryUsage();
client.gauge('nodejs.memory.rss', memUsage.rss);
client.gauge('nodejs.memory.heap_used', memUsage.heapUsed);
client.gauge('nodejs.memory.heap_total', memUsage.heapTotal);
}
# 4. Custom Memory Alerting
class MemoryAlert {
constructor(threshold = 100 * 1024 * 1024) {
this.threshold = threshold;
this.alertSent = false;
}
check() {
const memUsage = process.memoryUsage();
if (memUsage.heapUsed > this.threshold && !this.alertSent) {
this.sendAlert(memUsage);
this.alertSent = true;
} else if (memUsage.heapUsed < this.threshold * 0.8) {
this.alertSent = false;
}
}
sendAlert(memUsage) {
console.log(`Memory alert: ${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`);
// Send to monitoring service
}
}
# 5. Memory Health Check Endpoint
const express = require('express');
const app = express();
app.get('/health/memory', (req, res) => {
const memUsage = process.memoryUsage();
const heapStats = require('v8').getHeapStatistics();
const health = {
status: 'healthy',
memory: {
rss: Math.round(memUsage.rss / 1024 / 1024),
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024),
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
heapSizeLimit: Math.round(heapStats.heap_size_limit / 1024 / 1024)
},
timestamp: new Date().toISOString()
};
// Check if memory usage is too high
if (memUsage.heapUsed > 200 * 1024 * 1024) {
health.status = 'warning';
}
if (memUsage.heapUsed > 500 * 1024 * 1024) {
health.status = 'critical';
}
res.json(health);
});
# 6. Memory Leak Detection in Production
class ProductionLeakDetector {
constructor() {
this.samples = [];
this.maxSamples = 100;
}
sample() {
const memUsage = process.memoryUsage();
this.samples.push({
timestamp: Date.now(),
heapUsed: memUsage.heapUsed
});
if (this.samples.length > this.maxSamples) {
this.samples.shift();
}
this.checkForTrend();
}
checkForTrend() {
if (this.samples.length < 10) return;
const recent = this.samples.slice(-10);
const trend = this.calculateTrend(recent);
if (trend > 0.1) { // 10% increase per sample
console.log(`Memory leak trend detected: ${trend * 100}% increase`);
}
}
calculateTrend(samples) {
const first = samples[0].heapUsed;
const last = samples[samples.length - 1].heapUsed;
return (last - first) / first;
}
}
# 7. Automated Memory Cleanup
class MemoryCleanup {
constructor() {
this.cleanupTasks = [];
this.interval = setInterval(() => this.cleanup(), 60000); // Every minute
}
addCleanupTask(task) {
this.cleanupTasks.push(task);
}
cleanup() {
const memUsage = process.memoryUsage();
if (memUsage.heapUsed > 100 * 1024 * 1024) { // 100MB threshold
console.log('Running memory cleanup...');
this.cleanupTasks.forEach(task => {
try {
task();
} catch (error) {
console.error('Cleanup task failed:', error);
}
});
// Force garbage collection if available
if (global.gc) {
global.gc();
}
}
}
}
Summary
Node.js memory leak debugging involves several key components:
- Common Leak Types: Event listeners, closures, timers, and circular references
- Profiling Tools: Chrome DevTools, Clinic.js, and built-in Node.js profilers
- Optimization Strategies: Prevention techniques and optimization methods
- Production Monitoring: Real-time monitoring and alerting systems
Need More Help?
Struggling with Node.js memory leaks or need help implementing memory monitoring? Our Node.js experts can help you debug and optimize your application's memory usage.
Get Memory Debugging Help