垃圾回收
V8 GC、memory-leaks 直觉。
25 分钟
垃圾回收
Node 使用 V8 引擎管理堆内存。写长期运行的 API 或脚本时,不懂 GC 就难以解释 RSS 上涨、OOM、间歇性卡顿——本课建立直觉,衔接 nrt-apm 与 nrt-debug。
学习目标
- 区分 heap 与 RSS、新生代与老年代(Scavenge vs Mark-Sweep)
- 识别常见 memory leak 模式(全局缓存、闭包、事件监听未移除)
- 使用
process.memoryUsage与--expose-gc做简单观测
V8 堆结构(直觉版)
| 区域 | 说明 |
|---|---|
| 新生代 (New Space) | 短生命周期对象,Scavenge 复制回收,频繁但快 |
| 老生代 (Old Space) | 晋升后的长生命周期对象,Mark-Sweep / Compact |
| 大对象 | 可能直接进老生代 |
Full GC 停顿主线程——大 heap 或老对象多时,可能造成 几十毫秒级卡顿(P99 尖刺)。CPU 密集 + 大量分配会加剧 GC 压力。
观测内存
const mu = process.memoryUsage();
console.log({
rss: Math.round(mu.rss / 1024 / 1024) + " MB",
heapUsed: Math.round(mu.heapUsed / 1024 / 1024) + " MB",
heapTotal: Math.round(mu.heapTotal / 1024 / 1024) + " MB",
external: Math.round(mu.external / 1024 / 1024) + " MB",
});
- heapUsed 上涨不一定泄漏——可能是合法缓存。
- rss 长期涨而 heap 稳定,可能是 native 内存(Buffer、C++ addon)或碎片。
开发时可 node --trace-gc app.js 看 GC 日志;生产慎用高频 trace。
常见泄漏模式
- 全局 Map 只增不减:按 userId 缓存响应,无 TTL/LRU。
- EventEmitter 重复
on不off:见 nva-events。 - 闭包持有大对象:回调引用了本可释放的 scope。
- Timer 未 clear:nrt-timers 里的 interval 泄漏。
- Detached DOM 类比:Node 里类似 未销毁的 Server/Timer/Handle 阻止进程退出。
// 反模式
const cache = new Map();
app.use((req, res, next) => {
cache.set(req.ip, res); // 永不删除
next();
});
修复:WeakMap(键必须是对象)、TTL、最大条目、进程外 Redis。