当前位置: 首页 > news >正文

[测试] Node.js 进程内存泄漏排查:从 heapdump 到根因修复

前言

最近在生产环境遇到一个 Node.js 服务内存持续增长的问题,RSS 从启动时的 120MB 在 72 小时内涨到 1.2GB,触发了 OOM Killer。说实话,内存泄漏排查是后端开发中最让人头疼的问题之一,这次踩了不少坑,记录一下完整的排查过程。## 1. 现象确认通过 Prometheus 监控发现process_resident_memory_bytes呈线性增长,且 GC 后不回落:javascript// 快速确认:在进程内打印堆内存setInterval(() => { const mem = process.memoryUsage(); console.log({ rss: (mem.rss / 1024 / 1024).toFixed(1) + "MB", heapUsed: (mem.heapUsed / 1024 / 1024).toFixed(1) + "MB", external: (mem.external / 1024 / 1024).toFixed(1) + "MB", });}, 30000);观察发现heapUsed在稳步增长,说明是 JS 堆内泄漏而非 native 插件问题。## 2. 抓取 Heapdump使用v8.writeHeapSnapshot()在不同时间点抓两份快照进行对比:javascriptconst v8 = require('v8');// 启动 5 分钟后抓第一份setTimeout(() => { v8.writeHeapSnapshot('/tmp/heap-t1.heapsnapshot');}, 5 * 60 * 1000);// 启动 30 分钟后抓第二份setTimeout(() => { v8.writeHeapSnapshot('/tmp/heap-t2.heapsnapshot');}, 30 * 60 * 1000);## 3. Chrome DevTools 对比分析在 Chrome DevTools → Memory 面板加载两份快照,选择Comparison视图,按Delta排序。关键发现:-(string)类型增量最大(+48000 个对象)- Retainer 路径指向一个Map对象,key 是请求 ID- 这个 Map 挂在一个全局的requestCache上## 4. 根因定位追踪到代码中一个请求缓存,只有 set 没有 delete:javascript// 问题代码:只进不出的缓存const requestCache = new Map();app.use((req, res, next) => { const reqId = generateId(); requestCache.set(reqId, { url: req.url, startTime: Date.now(), headers: req.headers, // 每个请求的 headers 对象 ~2KB }); next();});每个请求往 Map 里塞约 2KB 数据,QPS 100 的情况下,每小时增长 ~700MB。## 5. 修复方案个人觉得最简单有效的方案是加 TTL 淘汰:javascript// 修复:加 TTL + 定期清理const requestCache = new Map();const CACHE_TTL = 60 * 1000; // 1 分钟setInterval(() => { const now = Date.now(); for (const [key, val] of requestCache) { if (now - val.startTime > CACHE_TTL) { requestCache.delete(key); } }}, 30 * 1000);部署后 RSS 稳定在 150MB 左右,72 小时无增长。## 小结内存泄漏排查的核心步骤:监控确认 → heapdump 对比 → Retainer 追踪 → 定位无界增长的数据结构。大多数 Node.js 内存泄漏都是因为某个集合(Map / Set / Array)只增不减,加个 TTL 或 LRU 就能解决。

http://www.jsqmd.com/news/842139/

相关文章:

  • PPO 算法在 RLHF 中的应用:让模型学会理解人类偏好
  • 终极GBFR Logs指南:掌握碧蓝幻想Relink伤害分析的完整教程
  • 2026年5月铝网板采购指南:如何甄选实力与口碑兼具的源头厂家 - 2026年企业推荐榜
  • WarcraftHelper:魔兽争霸3终极兼容性增强插件完整指南
  • 硬件入门 + 单片机基础(第12天)MQTT协议零基础详解
  • CodeTree:可视化分析代码仓库目录结构,提升项目可维护性
  • QT ToolButton的5个隐藏技巧与3个常见坑,新手避雷指南(基于Qt 6.5)
  • 构建Web化配置中心:从环境变量管理到实时热更新的工程实践
  • 从零到精通:大模型产品经理的实战学习路线图!
  • 大语言模型在模块化布局优化中的应用与实战
  • NC费用报销与银企直联支付避坑指南:从单据流转到支付成功的完整配置
  • Browser-Use 实战指南:让 AI 自己操控浏览器的 7 个实用场景
  • 3.3V供电,实测5mA!KT6368A蓝牙5.1透传模块开箱上电全记录
  • 对比官方直连体验Taotoken在模型切换与路由上的便利
  • MATLAB仿真GPS调制和捕获
  • 3种智能解析技术:VideoDownloadHelper如何突破网页视频下载限制
  • 终极Gerber文件查看器Gerbv:免费开源PCB设计验证的5大优势
  • NPJ Precis Oncol(IF=8)中国科学院深圳先进技术研究院吴红艳教授等团队:深度可解释放射基因组学解析乳腺MRI肿瘤微环境
  • 基于加速度计的体感音乐控制器:用MakeCode与Circuit Playground Express实现交互式乐器
  • 2026四川存储服务器公司TOP名录:国产gpu服务器厂家/国产存储服务器厂家/国产服务器价格表/国产服务器供应商/选择指南 - 优质品牌商家
  • 手把手教你为Vue3项目集成OnlyOffice 9.3:从配置到回调保存的完整实战
  • NotebookLM如何让AI替你精准定位审稿人潜台词?——基于572份Accepted回复文本的NLP语义聚类分析
  • 「全场景适用」2026最新论文去机器味指南:3款工具红黑榜与5个核心提示词
  • 2026年云南柔性防护网制造厂深度解析:如何选择专业可靠的合作伙伴 - 2026年企业推荐榜
  • 如何快速掌握炉石传说游戏自动化:开源智能助手完整教程
  • Display Driver Uninstaller:显卡驱动清理的终极解决方案
  • 从零打造会发光的航天飞机模型:焊接入门与PCB组装实战
  • 性价比高的激光切割机怎么选?这些品牌值得你深入了解!
  • 特斯拉Model 3无线充电垫DIY:基于Qi标准与3D打印的集成方案
  • 树莓派复古游戏系统搭建:从GPIO控制到RetroPie模拟器实战