你的浏览器无法正常显示内容,请更换或升级浏览器!

Node.js 内存泄漏排查实战指南

tenfei
tenfei
发布于2026-08-16 11:32 阅读10次
Node.js 内存泄漏排查实战指南
系统讲解 Node.js 内存泄漏的排查方法:从识别泄漏征兆、采集堆快照、Chrome DevTools 对比分析,到闭包、事件监听器、定时器、缓存无限增长等常见泄漏场景的修复,附完整代码示例和压测验证流程。
# Node.js 内存泄漏排查实战指南 ## 前言 Node.js 服务跑久了内存慢慢涨、最终 OOM 崩溃,是很多后端开发者都遇到过的头疼问题。内存泄漏不像报错那样直接暴露,往往要运行几小时甚至几天才显现。本文整理一套完整的排查方法和实战案例。 ## 内存泄漏的典型征兆 - 服务内存占用随时间持续上升,且不会回落 - 定时 GC 后内存仍居高不下 - 用户量增加时,内存涨幅远超预期 - 频繁触发 OOM,进程被系统 kill 先确认是不是真的泄漏:有时内存上涨只是因为缓存预热、连接池扩容,属于正常现象。 ## 第一步:采集堆快照 在疑似泄漏的时间点,采集多份堆快照做对比。 ```javascript // 方式一:运行时用 inspector 模块手动触发 const inspector = require("inspector"); const fs = require("fs"); function takeHeapSnapshot() { const session = new inspector.Session(); session.connect(); const fd = fs.openSync(`./heap-${Date.now()}.heapsnapshot`, "w"); session.on("HeapProfiler.addHeapSnapshotChunk", (m) => { fs.writeSync(fd, m.params.chunk); }); session.post("HeapProfiler.takeHeapSnapshot", () => { session.disconnect(); fs.closeSync(fd); }); } ``` 生产环境更推荐用 `--inspect` 启动,通过 Chrome DevTools 的 Memory 面板远程采集: ```bash node --inspect=0.0.0.0:9229 server.js ``` ## 第二步:对比快照找增长点 在 Chrome DevTools 中: 1. 打开 Memory 面板 → Heap snapshot 2. 采集第一份快照,等一段时间(或压测) 3. 采集第二份快照 4. 切换视图到 Comparison(对比)模式 5. 按 Delta 排序,找新增对象最多的构造函数 关注这几个常见元凶: - **Closure(闭包)**:闭包引用大对象未释放 - **EventEmitter**:监听器不断增加 - **Array / Object**:全局缓存无限增长 - **Socket / Stream**:连接未正确销毁 ## 常见泄漏场景与修复 ### 场景1:全局缓存无限增长 ```javascript // 错误:缓存不设上限,永不清理 const cache = new Map(); function getData(key) { if (!cache.has(key)) { cache.set(key, expensiveCompute(key)); } return cache.get(key); } // 修复:用 LRU 缓存,限制容量 const LRU = require("lru-cache"); const cache = new LRU({ max: 500, // 最多500条 ttl: 1000 * 60 * 5, // 5分钟过期 }); ``` ### 场景2:事件监听器未移除 ```javascript // 错误:每次请求都添加监听器 function handleRequest(req) { emitter.on("data", (d) => process(d)); } // 修复:用 once 或显式移除 function handleRequest(req) { const handler = (d) => { process(d); emitter.removeListener("data", handler); }; emitter.on("data", handler); } // 更好的方式:emitter.once("data", handler) ``` 可以用 `process.getMaxListeners` 检查,配合 `events.setMaxListeners` 防止监听器堆积。 ### 场景3:定时器未清理 ```javascript // 错误:setInterval 未清除 setInterval(() => checkStatus(), 1000); // 修复:保存引用,用完清除 const timer = setInterval(() => checkStatus(), 1000); // 需要停止时 clearInterval(timer); ``` ### 场景4:数据库连接未释放 ```javascript // 错误:每个请求新建连接但不关闭 const client = new MongoClient(url); await client.connect(); // 用完没 close,连接对象累积 // 修复:用连接池(客户端内置),复用连接 // 并在应用退出时统一关闭 process.on("SIGTERM", async () => { await client.close(); process.exit(0); }); ``` ## 第三步:用 heapdump + Chrome 分析 ```bash npm install heapdump ``` ```javascript // 在需要的地方触发快照 const heapdump = require("heapdump"); heapdump.writeSnapshot("./snapshot.heapsnapshot"); ``` 然后 Chrome DevTools → Memory → Load 加载快照文件分析。 ## 第四步:压测验证 修复后用工具压测,观察内存曲线是否平稳: ```bash # 用 autocannon 压测 npx autocannon -c 100 -d 60 http://localhost:3000 # 或用 wrk wrk -t12 -c400 -d60s http://localhost:3000 ``` 压测同时用 `process.memoryUsage()` 记录: ```javascript setInterval(() => { const m = process.memoryUsage(); console.log("heapUsed:", Math.round(m.heapUsed / 1024 / 1024) + "MB"); }, 5000); ``` 如果压测后 heapUsed 能回落,说明泄漏已修复。 ## 排查流程总结 | 步骤 | 操作 | |------|------| | 1 | 确认是否真泄漏 | | 2 | 采集多份堆快照 | | 3 | Comparison 对比找增长 | | 4 | 定位到具体代码 | | 5 | 修复(缓存上限/移除监听/清理定时器) | | 6 | 压测验证 | ## 小结 内存泄漏排查靠的是"对比"而非"猜":多份快照对比、压测前后对比。掌握 heap snapshot 和 Comparison 视图,配合对闭包、监听器、定时器、缓存这几类常见元凶的敏感度,大部分泄漏都能快速定位。

2

0

文章点评
Copyright © from 2021 by namoer.com
458815@qq.com QQ:458815
蜀ICP备2022020274号-2