内存泄漏是指不再需要但从未释放的内存,因为某些东西仍然引用它。随着时间的推移,进程的堆会增长,直到速度变慢或崩溃(内存不足)。在长期运行的 Node 服务器中,泄漏是一个严重的生产问题。
常见的泄漏来源
js
cache = ();
app.(, { cache.(req.., data); });
emitter.(, handler);
( {...}, );
() {
huge = ();
huge.;
}
共同的线索是:一个可到达的引用(一个全局 Map、一个活跃的监听器、一个运行的定时器、一个闭包)阻止对象被垃圾回收,即使它们不再有用。
// 1. Watch heap growth over time — steady upward trend = likely leak
setInterval(() => {
const { heapUsed } = process.memoryUsage();
console.log(`heap: ${(heapUsed / 1e6).toFixed(1)} MB`);
}, 10000);
# 2. Heap snapshots — capture at two points, compare what grew
node --inspect app.js # then use Chrome DevTools → Memory → heap snapshots
# or the heapdump / clinic.js tools
诊断过程:确认堆稳定增长(不仅仅是正常波动),然后在工作负载前后进行堆快照并进行比较,以找出哪些对象类型在积累("保留大小"在增加)。
// ✅ bound caches with TTL/size limits (LRU)
import { LRUCache } from "lru-cache";
const cache = new LRUCache({ max: 1000, ttl: 1000 * 60 }); // evicts automatically
// ✅ always remove listeners and clear timers
emitter.off("data", handler);
clearInterval(timer);
// ✅ use WeakMap/WeakSet for metadata that shouldn't keep objects alive
const meta = new WeakMap(); // entries are GC'd when the key object is
修复方法反映了原因:有界缓存(具有最大大小/TTL 的 LRU)、始终将监听器/定时器的创建与清理配对,并使用 WeakMap/WeakSet 来存储不应该在内存中固定对象的对象键元数据。
内存泄漏在 Node 中特别危险,因为服务器是长期运行的——一个在开发中不可见的缓慢泄漏会逐渐降低性能,最终导致生产进程崩溃(通常会重启并失去进行中的工作)。
了解经典来源(无界缓存、未移除的监听器/定时器、持续的闭包)、如何检测它们(堆监控 + 使用 DevTools/clinic.js 进行快照比较),以及修复方法(有界 LRU 缓存、规范的清理、WeakMap)对于长期运行可靠、稳定的 Node 服务至关重要。