Fix Memory Leaks in Node.js Applications
Problem
Node.js application memory usage keeps growing over time, eventually causing the application to crash or become unresponsive.
Root Cause
Memory leaks in Node.js typically occur due to unclosed event listeners, retaining references to large objects, circular references, or improper cleanup of timers and streams.
Solution
Identify and fix common Node.js memory leak patterns:
Step 1: Monitor Memory Usage
// Add memory monitoring
function logMemoryUsage() {
const used = process.memoryUsage();
console.log('Memory Usage:');
for (let key in used) {
console.log(`${key}: ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`);
}
}
// Log every 30 seconds
setInterval(logMemoryUsage, 30000);
Step 2: Fix Event Listener Leaks
// ❌ Problem - event listeners not removed
function createUser(userId) {
const user = new EventEmitter();
// This listener is never removed
user.on('update', handleUpdate);
return user;
}
// ✅ Solution - properly remove listeners
function createUser(userId) {
const user = new EventEmitter();
function handleUpdate(data) {
// Handle update
}
user.on('update', handleUpdate);
// Cleanup function
user.cleanup = () => {
user.removeListener('update', handleUpdate);
user.removeAllListeners();
};
return user;
}
Step 3: Fix Timer and Interval Leaks
// ❌ Problem - timers not cleared
function startPolling() {
setInterval(() => {
fetchData();
}, 5000);
}
// ✅ Solution - clear timers properly
function startPolling() {
const intervalId = setInterval(() => {
fetchData();
}, 5000);
// Return cleanup function
return () => clearInterval(intervalId);
}
// Usage
const stopPolling = startPolling();
// Later, when no longer needed:
stopPolling();
Step 4: Handle Stream Cleanup
// ❌ Problem - streams not properly closed
function processFile(filename) {
const readStream = fs.createReadStream(filename);
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);
// Streams might not be properly closed
}
// ✅ Solution - ensure proper cleanup
function processFile(filename) {
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(filename);
const writeStream = fs.createWriteStream('output.txt');
readStream.on('error', (err) => {
cleanup();
reject(err);
});
writeStream.on('error', (err) => {
cleanup();
reject(err);
});
writeStream.on('finish', () => {
cleanup();
resolve();
});
function cleanup() {
readStream.destroy();
writeStream.destroy();
}
readStream.pipe(writeStream);
});
}
Step 5: Use Memory Profiling Tools
# Install clinic.js for memory profiling
npm install -g clinic
# Profile your application
clinic doctor -- node app.js
# Or use built-in Node.js inspector
node --inspect app.js
# Then open Chrome DevTools for memory profiling