如何定位 Node.js 的内存泄漏
基础知识
Node.js 进程的内存管理,都是有 V8 自动处理的,包括内存分配和释放。那么 V8 什么时候会将内存释放呢?
在 V8 内部,会为程序中的所有变量构建一个图,来表示变量间的关联关系,当变量从根节点无法触达时,就意味着这个变量不会再被使用了,就是可以回收的了。
而这个回收是一个过程性的,从快速 GC 到 最后的 Full GC,是需要一段时间的。
另外,Full GC 是有触发阈值的,所以可能会出现内存长期占用在一个高值,也可以算是一种内存泄漏,可以从《一次 Node.js 应用内存暴涨分析》中找到例子。还有一种就是引用不释放,导致无法进入 GC 环节,并且一直产生新的占用,这一般会发生在 Javascript 层面。
所以,定位内存泄漏问题,一般方案就是找那些不被使用又不会被释放的变量,处理了这些变量,问题一般就可以解决了。如果是 Node.js 底层变量不释放,除了提交 issue 等待解决外,只能通过优化启动参数来解决。
如何找出并解决问题
工具
工欲善其事必先利其器,在排查时,我们还是需要一些工具来帮忙的。
devTool
这个是今年初出的 Node.js 调试工具,基于 Electron 将 Node.js 和 Chromium 的功能融合在了一起。操作起来比 node-inspector 方便,开放的 Timeline 功能还是比较实用的,虽然不是实时显示。
仅需要 devtool xxx.js,还可以通过 .devtoolrc 来进行参数定制,具体见 GitHub
heapdump + chrome devTool
这个是比较传统的定位内存泄漏的组合。heapdump 可以直接在代码中调用生成内存快照,然后将快照文件导入到 chrome devTool 进行分析,之后操作其实和前者就差不多了。不过,这个方案和前者有一点区别就是,前者实际还是在浏览器环境中,所以生成的内存快照会有一些 DOM 对象的存在,会有一定的干扰。而这个方案,是直接调用底层 V8 的方法,生成的快照只有 Node.js 环境中的对象。
memwatch
这个可以在代码里直接使用,实时检测内存动态,当发生内存泄漏的时候,会触发 ‘leak’ 事件,会传递当前的堆状态,配合 heapdump 有奇效。详见 memwatch。
流程
一、重现问题
对于垃圾回收,V8 引擎有很复杂的逻辑来决定什么时候进行回收。很多时候,当我们发现 Node.js 进程所使用的内存快速增长的时候,并不能确定是否是内存泄漏导致的,很有可能是程序设计问题,导致内存的不合理利用。只有当垃圾回收触发,未使用内存被释放后,内存增长还在持续,我们才能确定是发生了内存泄漏。
隐藏的内存泄漏问题,大多是有触发条件的,重现问题是需要这些条件的,所以我们在平时写代码的时候,可以将一些重要环节的参数细节打印在 log 中,这样我们在重现问题是就不会摸不着头脑,乱试一气。
有了参数可以用来重现问题,接下来要确定问题。我们要确定,这部分内存是否没有被 GC 正确释放。那么问题来了,我们如何知道程序进行了垃圾回收呢?很显然,等待并不是办法,我们要主动。
在 Node.js 的启动参数中,提供了暴露手动调用 GC 方法的参数,即 --expose-gc。我们用这个参数来启动应用后,就可以在代码中调用 global.gc() 手动触发垃圾回收操作。同时,使用 process.memoryUsage().heapUsed 获取进程运行时所占用的内存。如果 GC 之后,内存依然没有下降,就可以确定是内存泄露了。
二、生成内存快照
既然内存是问题,我们就需要获取程序运行的内存快照来帮助定位问题。但内存快照并不是随便打得,是有一定技巧的。
我们至少要生成三次内存快照,才能更好的定位问题。这三次中又一次要在问题出现前生成,之后可以在问题持续的过程中生成两次或更多。
为什么要这样做呢?理解起来很简单。第一次是为了获取正常情况下的堆栈信息,而在问题出现后,堆栈信息一定会发生变化,有了第一次的信息,我们才好进行后面的比对,过滤一些无用的信息。而后两次的快照,用来比对某一对象的堆栈变化,来确定是否是有问题的对象。下面会详细应用到。
三、定位问题
用 devTool 的可以忽略下面的过程:
打开 Chrome Devtools ,进入到 Profiles 选项卡,点 Load 按钮,加载之前生成的快照。
对于内存快照,有四个视图,Summary,Comparison,Containment,Statistics,这里面常用的是前三个。
在 Summary 视图中,我们可以看到当前快照的全部信息,以及多个快照之间的信息。在列表里显示的都是对象的构造函数名字,可以先忽略被括号包裹的对象,优先观察其他的对象,最后再来看他们。后面的 shallow size 表示的是对象自身的大小,retained size 表示的是对象和它依赖对象的大小,一般是 GC 不可达的。
在 Comparison 视图中,我们可以进行多个快照之间的对比,这个用处比较大,如果我们将前两次快照进行对比,可能比较快速的定位出问题的对象。注意观察 New、Deleted、Delta,如果是内存泄漏的对象,可能是一直在 New,而没有 Deleted。
在 Containment 视图中,我们可以查看整个 GC 路径,当然一般不会用到。因为展开在 Summary 和 Comparison 列举的每一项,都可以看到从 GC roots 到这个对象的路径。通过这些路径,你可以看到这个对象的句柄被什么持有,从而定位问题产生的原因。值的注意的是,其中背景色黄色的,表示这个对象在 Javascript 中还存在引用,所以可能没有被清除。如果是红色的,表示的是这个对象在 Javascript 中不存在引用,但是依然存活在内存中,一般常见于 DOM 对象,它们存放的位置和 Javascript 中对象还是有不同的,在 Node.js 中很少遇见。
更多的操作方法,可以看这个视频 Memory Profiling with Chrome DevTools 和Memory Management Masterclass。还有 Chrome 的文档 Memory Profiling(旧) 和Memory Diagnosis(新)。讲的还是很详细的。(请自备梯子)
四、解决问题
一般在 Javascript 中存在引用而导致内存泄漏的情况,是比较好处理的,只需要在使用后及时的将引用释放掉即可。
但像 《一次 Node.js 应用内存暴涨分析》 所存在的那种内存问题,是属于底层机制的问题,如果等不了 bugfix,就只能先通过一些启动参数来优化内存管理。常用的参数:
--max-old-space-size限制老生区大小,可以控制内存占用的最大值,即使发生泄漏,也不会让内存占用保持很高。可以根据开启进程数以及是否同机部署来优化。--gc_global这其实是个 V8 的 debug flag,让 GC 永远都是 Full GC,使用上会有一定的性能损耗,根据应用复杂度不同,损耗不同。
当我们找到问题,进行修复后,重复上面的步骤,确认问题已经被解决。有时可能一次并不能解决问题,所以耐心还是很重要的。
实战
可以在这里下载使用到的代码, GitHub,进入 memory-leak 文件夹。
我们来举个例子,应用上面的步骤排查问题,使用 leak-memory 的例子,代码还有另外一个例子,可以自己实践。
这里我们为了方便,我们使用了 devTool。
devTool leak-memory.js
然后在打开的界面中进入内存快照界面,生成第一次快照。当控制台有输出后,间隔的生成两次快照,结果如下。

我们切换视图,对比下三次快照间的区别,可以看到 Foo 这个对象一直在创建而没有被删除。


我们展开 Foo,选择下面的一个实例,查看它的 GC path,可以看到它一直被 neverRelease 持有引用(黄色),所以没有被释放,之后就可以进行问题的处理了。

去掉 // neverRelease.splice(index, 1); 前的注释,然后在重复上面的步骤,你会发现内存的变化已经正常了。
在使用 devTool 时,可以查看运行时的 memory timeline,如果图像呈现阶梯式增长,一般就是存在内存泄漏问题了。正常的应用曲线会类似于锯齿,如图:

总结
内存泄漏问题的定位,经验很重要,但有了良好工具的辅助,可以节省很多时间。如果懒得自己一步步的操作,可以接入 alinode,这个可以帮助你很方便的生成快照等运行时数据,并有一定的分析辅助,还是方便的。
你可能看到很多内存分析的文章会有一些图来表示内存的增长,可以使用 python 来快速生成相关的图片,使用
matplotlib.pyplot这个包。

参考
- memory-diagnosis
- Memory Profiling with Chrome DevTools
- Simple Guide to Finding a JavaScript Memory Leak in Node.js
- A tour of V8: Garbage Collection
如何定位 Node.js 的内存泄漏的更多相关文章
- Memlab,一款分析 JavaScript 堆并查找浏览器和 Node.js 中内存泄漏的开源框架
Memlab 是一款 E2E 测试和分析框架,用于发现 JavaScript 内存泄漏和优化机会. Memlab 是 JavaScript 的内存测试框架.它支持定义一个测试场景(使用 Puppete ...
- node.js之内存机制特性
Node.JS的V8引擎具有垃圾回收机制与内存限制的特性,V8的内存限制:64位系统约为1.4GB.32位系统约为0.7GB:V8采用基于分代式垃圾回收机制,堆内存结构分为新生代和老生代,新生代达到一 ...
- js的内存泄漏场景、监控以及分析
内存泄漏 Q:什么是内存泄漏? 字面上的意思,申请的内存没有及时回收掉,被泄漏了 Q:为什么会发生内存泄漏? 虽然前端有垃圾回收机制,但当某块无用的内存,却无法被垃圾回收机制认为是垃圾时,也就发生内存 ...
- 深入浅出Node.js (5) - 内存控制
5.1 V8的垃圾回收机制与内存限制 5.1.1 Node与V8 5.1.2 V8的内存限制 5.1.3 V8的对象分配 5.1.4 V8的垃圾回收机制 5.1.5 查看垃圾回收日志 5.2 高效使用 ...
- js造成内存泄漏的几种情况
1.介绍js的垃圾回收机制 js的垃圾回收机制就是为了防止内存泄漏的,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的 ...
- 技术干货丨通过wrap malloc定位C/C++的内存泄漏问题
摘要:用C/C++开发的程序执行效率很高,但却经常受到内存泄漏的困扰.本文提供一种通过wrap malloc查找memory leak的思路. 用C/C++开发的程序执行效率很高,但却经常受到内存泄漏 ...
- MAT工具定位分析Java堆内存泄漏问题方法
一.MAT概述与安装 MAT,全称Memory Analysis Tools,是一款分析Java堆内存的工具,可以快速定位到堆内泄漏问题.该工具提供了两种使用方式,一种是插件版,可以安装到Eclips ...
- Node.js进程内存使用查看方法及返回对象的含义
1 前言 使用process.memoryUsage() ,然后可以得到一个对象如下: var mem = process.memoryUsage(); console.log(mem); 结果: { ...
- Memory Analyzer Tool定位Java heap space内存泄漏
java heap space是一个很蛋疼的问题,如果开发调试时遇到还好,如果是在项目上线后运行一段时间后,才抛出该异常,那真的很悲剧(那你得找代码中到底是哪里内存泄露了),这真是一个悲伤的故事. 1 ...
随机推荐
- v-model和v-bind的区别
VUE学习篇1 Mustache (双大括号写法)不能在 HTML 属性中使用,应使用 v-bind 指令: <div v-bind:id="dynamicId">&l ...
- bzoj3280: 小R的烦恼(最小费用最大流)
Description 小R最近遇上了大麻烦,他的程序设计挂科了.于是他只好找程设老师求情.善良的程设老师答应不挂他,但是要 求小R帮助他一起解决一个难题.问题是这样的,程设老师最近要进行一项邪恶的实 ...
- 51nod 1812 树的双直径 题解【树形DP】【贪心】
老了-稍微麻烦一点的树形DP都想不到了. 题目描述 给定一棵树,边权是整数 \(c_i\) ,找出两条不相交的链(没有公共点),使得链长的乘积最大(链长定义为这条链上所有边的权值之和,如果这条链只有 ...
- python3 zip压缩
参考: https://docs.python.org/3/library/zipfile.html https://zhidao.baidu.com/question/149840976436638 ...
- kafka集群安装及简单使用
关于kafka是什么及原理,请参考kafka官方文档的介绍:http://kafka.apache.org/documentation/#introduction ,英文不好的同学可以看这里http: ...
- MVC流程
控制器:调用模型,并调用视图,将模型产生数据传递给视图,并让相关视图去显示 模 型:获取数据,并处理返回数据 视 图:是将取得的数据进行组织.美化等,并最终向用户终端输出 第一步 浏览者 -& ...
- Fiddler使用二(Fiddler抓取HTTP请求)
参考:http://blog.csdn.net/ohmygirl/article/details/17849983 Fiddler使用一中已经介绍了Fiddler的原理和软件界面.本文主要针对Fidd ...
- 【随笔】关于绝对定位absolute相对于父元素定位的问题
绝对定位absolute的官方定义: 设置为绝对定位的元素框从文档流完全删除,并相对于其包含块定位,包含块可能是文档中的另一个元素或者是初始包含块.元素原先在正常文档流中所占的空间会关闭,就好像该元素 ...
- ZOJ 3607 Lazier Salesgirl
Kochiya Sanae is a lazy girl who makes and sells bread. She is an expert at bread making and selling ...
- Spring Session解决分布式Session问题的实现原理
使用Spring Session和Redis解决分布式Session跨域共享问题 上一篇介绍了如何使用spring Session和Redis解决分布式Session跨域共享问题,介绍了一个简单的案例 ...