Node.js Memory Leaks debug
Node process hours pagh days Daq memory usage slowly climb, container orchestrator OOM-kill - frustrating bug class: locally reproduce pagh, crash qaSpa' logs normal legh, "restart pod" symptom disappear (fix pagh). leak actually tu' (work around neH pagh).
garbage-collected language Daq "leak"
Node manual memory management ghajbe' - leak missing free() pagh. object GC reachable consider (global scope, active closure, long-lived data structure) code logically finish qaSDI' je. GC collect laHbe' (you might still use it). bug reH "reference hold" ("release forget" pagh).
usual suspects
eviction pagh growing collections. Map pagh array cache (.set()/.push() neH, cleanup pagh):
// leaks: every request adds an entry, nothing ever removes one
const responseCache = new Map();
app.get('/api/report/:id', async (req, res) => {
if (!responseCache.has(req.params.id)) {
responseCache.set(req.params.id, await generateReport(req.params.id));
}
res.json(responseCache.get(req.params.id));
});
id unique Hoch memory Daq forever. fix: bound - LRU cache (least-recently-used entry evict size limit Dung) pagh TTL (entries fixed time qaSDI' expire).
event listeners attached thing Dung long live. Node EventEmitter listener Hoch strong reference hold. function repeatedly Qu' Daq listener attach, remove pagh vaj listeners accumulate - listener Hoch scope variables close over (alive keep).
// leaks: a new listener added every time this runs, old ones never removed
function watchConnection(connection) {
dbPool.on('error', (err) => {
logger.error(`Connection error: ${err.message}`, { connectionId: connection.id });
});
}
watchConnection 10,000 times call vaj dbPool 10,000 listeners hold, Hoch own connection object close over. fix: done vaj listener remove (emitter.off(event, handler)), pagh emitter.once() (wa'logh fire).
timers never cleared. setInterval closure (reference everything) alive keep timer Qu' Dung - default forever.
// leaks if `session` objects are meant to be short-lived
function startHeartbeat(session) {
setInterval(() => {
session.lastSeen = Date.now();
}, 30_000);
// no reference to the interval kept anywhere — it can never be cancelled
}
setInterval/setTimeout return value keep pagh vaj clearInterval/clearTimeout handle lose - timer closure process exit Dung live.
large objects closures (small piece neH poQ). function whole request pagh response close over (wa' field neH) vaj whole object (everything it references) closure exist Dung alive.
// keeps the entire `req` object alive inside `processedLater`
function schedule(req) {
const processedLater = () => console.log(req.user.id);
queue.push(processedLater);
}
// only keeps the one value you actually need
function schedule(req) {
const userId = req.user.id;
const processedLater = () => console.log(userId);
queue.push(processedLater);
}
leak real, normal GC sawtooth pagh confirm
healthy Node memory flat pagh - GC Qu' up down. leak trend: GC cycle qaSDI' baseline climb (peaks neH pagh).
setInterval(() => {
const { heapUsed, rss } = process.memoryUsage();
console.log(`heapUsed=${(heapUsed / 1024 / 1024).toFixed(1)}MB rss=${(rss / 1024 / 1024).toFixed(1)}MB`);
}, 60_000);
real traffic Daq hours log. heapUsed dip Hoch qaSDI' upward trend vaj leak; stable range oscillate vaj normal GC behavior.
heap snapshots leaking object find
leak Sov vaj nuq find tool --inspect 'ej Chrome DevTools heap profiler, load Daq minutes pIm cha' snapshots compare:
node --inspect server.js
Chrome Daq chrome://inspect poSmoH, process connect, Memory tab Daq:
- heap snapshot take (baseline).
- app realistic load minutes Qu' chaw'.
- second snapshot take.
- comparison view "Comparison" Daq switch - objects sort: new instances appeared 'ej collected pagh.
comparison top object type - suspiciously specific (own Session class pagh closure count growing) - leak almost always. DevTools retainer tree qatlh still reachable show: variable, array, Map reference hold (collection prevent). retainer path real answer - code line exact fix.
cheaper first pass: --max-old-space-size 'ej crash-time snapshots
debugger session Daq leak reproduce Qatlh vaj OOM time automatic heap snapshot tlhap:
node --max-old-space-size=512 --heapsnapshot-near-heap-limit=3 server.js
3 heap snapshots disk Daq write (process memory ceiling hit qaSpa'): production staging Daq Dun (live debugger attach laHbe') 'ach .heapsnapshot file grab, DevTools Daq load, same comparison analysis.
yaj
Node memory leak Hoch same root cause: code poQbe' reference hold. caches eviction pagh, listeners removal pagh, timers clearing pagh, closures too much capture - wej places reference almost always live. process.memoryUsage() trend confirm, heap snapshot comparison specific object find - symptoms neH guess real code line point rarely.