Tackling Memory Leaks in Node.js
Memory leaks in Node.js can be silent killers for your applications. They degrade performance, increase costs, and eventually lead to crashes. Let’s break down common causes and actionable strategies to prevent or fix them.
1?? References: The Hidden Culprits
// ?? Leak Example: Accidentally assigning to global scope
function processUserData(user) {
global.cachedUser = user; // Stored globally, never garbage-collected!
}
Fix: Use modules or closures to encapsulate data:
// ? Safe approach: Module-scoped cache
const userCache = new Map();
function processUserData(user) {
userCache.set(user.id, user);
}
// ?? Leak Example: Cached array with lingering references
const cache = [];
function processData(data) {
cache.push(data); // Data remains even if unused!
}
Fix: Use WeakMap for ephemeral references:
// ? WeakMap allows garbage collection when keys are removed
const weakCache = new WeakMap();
function processData(obj) {
weakCache.set(obj, someMetadata); // Auto-cleared if obj is deleted
}
2?? Closures & Scopes: The Memory Traps
// ?? Leak Example: Closure in a loop retains outer variables
for (var i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 1000); // All logs print "10"!
}
Fix: Use let or break the closure:
// ? let creates a block-scoped variable
for (let i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 1000); // Logs 0-9
}
// ?? Leak Example: Repeatedly loading a module
function getConfig() {
const config = require('./config.json'); // Re-loaded every call!
return config;
}
Fix: Load once at the top:
// ? Load once, reuse
const config = require('./config.json');
function getConfig() {
return config;
}
3?? OS & Language Objects: Resource Leaks
// ?? Leak Example: Forgetting to close a file
fs.open('largefile.txt', 'r', (err, fd) => {
// Read file but never close fd!
});
Fix: Always close resources:
// ? Cleanup with try-finally
fs.open('largefile.txt', 'r', (err, fd) => {
try {
// Read file...
} finally {
fs.close(fd, () => {}); // Ensure cleanup
}
});
// ?? Leak Example: Uncleared interval
const interval = setInterval(() => {
fetchData(); // Runs forever, even if unused!
}, 5000);
Fix: Clear timers when done:
// ? Clear interval on cleanup
function startInterval() {
const interval = setInterval(fetchData, 5000);
return () => clearInterval(interval); // Return cleanup function
}
const stopInterval = startInterval();
stopInterval(); // Call when done
领英推荐
4?? Events & Subscriptions: The Silent Accumulators
// ?? Leak Example: Adding listeners without removing
const emitter = new EventEmitter();
emitter.on('data', (data) => process(data)); // Listener persists forever!
Fix: Always remove listeners:
// ? Use named functions for removal
function onData(data) { process(data); }
emitter.on('data', onData);
emitter.off('data', onData); // Explicit cleanup
// ?? Leak Example: Anonymous function in event listener
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
setInterval(() => {
myEmitter.on('data', (message) => {
console.log('Received:', message);
});
}, 1000);
setInterval(() => {
myEmitter.emit('data', 'Hello, world!');
}, 3000);
Fix: Use once() for one-time events:
// ? Auto-remove after firing
setInterval(() => {
myEmitter.once('data', (message) => {
console.log('Received:', message);
});
}, 1000);
5?? Cache: A Double-Edged Sword
// ?? Leak Example: Cache with no limits
const cache = new Map();
function getData(key) {
if (!cache.has(key)) {
cache.set(key, fetchData(key)); // Grows forever!
}
return cache.get(key);
}
Fix: Use an LRU cache with TTL:
// ? npm install lru-cache
import LRUCache from 'lru-cache';
const cache = new LRUCache({ max: 100, ttl: 60 * 1000 }); // Limit to 100 items, 1min TTL
6?? Mixins: The Risky Extensions
// ?? Leak Example: Adding to Object.prototype
Object.prototype.log = function() { console.log(this); };
// All objects now have `log`, causing confusion and leaks!
Fix: Use utility functions instead:
// ? Safe utility module
const logger = {
log: (obj) => console.log(obj)
};
logger.log(user); // No prototype pollution
7?? Concurrency: Worker & Process Management
// ?? Leak Example: Forgetting to terminate a worker
const { Worker } = require('worker_threads');
const worker = new Worker('./task.js');
// Worker runs indefinitely!
Fix: Track and terminate workers:
// ? Cleanup with a pool
const workers = new Set();
function createWorker() {
const worker = new Worker('./task.js');
workers.add(worker);
worker.on('exit', () => workers.delete(worker));
}
// Terminate all on shutdown
process.on('exit', () => workers.forEach(w => w.terminate()));
?? Pro Tips for Prevention
Memory leaks are inevitable in complex systems, but with vigilance and the right practices, you can keep them in check. ??