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

Node.js 内存泄漏排查实战:从 heapdump 到定位根因

tenfei
tenfei
发布于2026-09-09 20:13 阅读17次
Node.js 内存泄漏排查实战:从 heapdump 到定位根因
线上 Node.js 服务运行几天内存就涨满,重启才好?本文记录一次真实内存泄漏的排查全过程:从监测曲线、生成堆快照、用 Chrome DevTools 对比分析,到最终定位并修复闭包引用的根因,还总结了常见泄漏模式和预防工具,适合用 Node 写常驻服务的同学参考。
# 一、现象:内存只涨不降 一个常驻的 Node.js 采集服务,启动时 RSS 约 120MB,跑满三天后飙到 1.8GB,日志开始频繁报 ECONNREFUSED。起初怀疑是连接数堆积,重启能顶一天,但问题反复出现,典型的**内存泄漏**特征:堆内存随运行时间持续增长,且 GC 后仍不回落。 > 经验:先区分"正常缓存增长"和"泄漏"——前者一般增长到某个平台期就稳定,后者是**单调不减**直到 OOM。 # 二、先看曲线再下手 在进程里加一段定时采样,把 RSS 和 heapUsed 打点输出: ```js setInterval(() => { const m = process.memoryUsage(); console.log(Date.now(), Math.round(m.rss/1048576), Math.round(m.heapUsed/1048576)); }, 60000).unref(); ``` 画出来发现 heapUsed 稳步爬升而 external 不动,方向锁定在**堆内存里的对象引用**,而不是 Buffer 这类外部资源。 # 三、用 heapdump 抓取快照 项目里装 heapdump,运行时抓两针: ```bash npm i heapdump kill -USR2 <pid> # 默认触发一次快照 ``` 在**内存涨到高峰时**抓一张 heap1.heapsnapshot,然后手工触发一次全局 GC 再抓 heap2,两次对比最关键。 # 四、DevTools 对比定位 把两张快照拖进 Chrome 的 Memory 面板,用 **Comparison** 视图看对象增量。结果一目了然:`EventEmitter` 数量涨了 4 万多个,全部挂在同一个业务对象的 `_events` 上。 顺着引用链往上,找到问题代码——在循环里给同一个 `request` 事件**重复注册监听器且从未移除**: ```js // 错误写法:每次轮询都 new listener 并 addListener client.on("data", onData); // 应该用 once 或先 removeListener ``` 改用 `client.once("data", onData)` 并在回调内重新订阅,泄漏消失。 # 五、常见泄漏模式速查 1. **事件监听器只增不减**——最普遍,检查 addListener/on 是否成对 removeListener; 2. **闭包持有大对象**——回调被长期缓存,间接引用数组/缓存 Map; 3. **定时器没清理**——setInterval/setTimeout 忘 clear,对象永不释放; 4. **全局变量/模块缓存**——无意把请求数据挂到全局或 require 模块顶层; 5. **Buffer/Stream 未消费**——data 事件监听但从未 end,配合背压问题。 # 六、预防建议 引入 `node --heapsnapshot-near-heap-limit=2` 做监控兜底,CI 里跑 memlab 做回归测试,长驻服务加**心跳+自动重启**的健康检查。把 heapdump 和采样脚本沉淀成团队的排查 SOP,比每次现场抓瞎高效太多。

2

0

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