一、现象
1. 如下报错
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
1: node::Abort() [/usr/local/bin/node]
2: node::OnFatalError(char const*, char const*) [/usr/local/bin/node]
3: v8::internal::V8::FatalProcessOutOfMemory(char const*, bool) [/usr/local/bin/node]
4: v8::internal::Factory::NewFixedArray(int, v8::internal::PretenureFlag) [/usr/local/bin/node]
5: v8::internal::OrderedHashTable<v8::internal::OrderedHashMap, 2>::Rehash(v8::internal::Handle<v8::internal::OrderedHashMap>, int) [/usr/local/bin/node]
6: v8::internal::Runtime_MapGrow(int, v8::internal::Object**, v8::internal::Isolate*) [/usr/local/bin/node]
7: 0x2ecd2d1042fd
8: 0x2ecd2d1b251e
2.在linux服务器上输入top命令,发现该进程memery占用居高不下。
二、内存泄漏介绍
由上述问题可以确认,该node服务内存泄露. Node.js 进程的内存管理,都是有 V8 自动处理的,包括内存分配和释放。那么 V8 什么时候会将内存释放呢?
在 V8 内部,会为程序中的所有变量构建一个图,来表示变量间的关联关系,当变量从根节点无法触达时,就意味着这个变量不会再被使用了,就是可以回收的了。 而这个回收是一个过程性的,从快速 GC 到 最后的 Full GC,是需要一段时间的。 另外,Full GC 是有触发阈值的,所以可能会出现内存长期占用在一个高值,也可以算是一种内存泄漏,可以从《一次 Node.js 应用内存暴涨分析》中找到例子。还有一种就是引用不释放,导致无法进入 GC 环节,并且一直产生新的占用,这一般会发生在 Javascript 层面。
所以,定位内存泄漏问题,一般方案就是找那些不被使用又不会被释放的变量,处理了这些变量,问题一般就可以解决了。如果是 Node.js 底层变量不释放,除了提交 issue 等待解决外,只能通过优化启动参数来解决。
三、问题定位
1、看代码,代码里面是否存在闭包或者变量声明或者引用不当的地方。
2、是否引用node-canvas 或者echart等大型消耗cpu内存资源等依赖包
3、借助工具来分析,比如heapdump + chrome浏览器 memery分析工具
四、问题修复以heapdump为例,分析并修复内存泄漏的问题(基于express项目)。
1、项目根目录下安装heapdump
// node version > v0.8
npm install heapdump
// Or, if you are running node.js v0.6 or v0.8:
npm install heapdump@0.1.0
2、入口文件引入这个heapdump
// express中,在app.js中加入
const heapdump = require('heapdump');
3、重启该express服务,top(mac 上通过 top -pid xxx 查看) 命令查看linux占用情况:,
并发起少数几次请求后,输入如下命令,生成heap快照文件,记作name1
kill -USR2 <pid>
4、模拟批量服务请求
由于我这个是可以提供给web调用的接口,因此用一个抓包工具charles(或者其他模拟请求工具都行),重复发1000次请求,在用top命令查看该进程memery占用情况。发现问题可以重现了。同时输入如下命令:
// in UNIX platforms
kill -USR2 <pid>
// 执行上述命令,会生成一下heap快照的文件,记作name2
5、拷贝name1、name2文件到本地,并在chrome浏览器中打开分析;
6 、经过上述对比分析,可以得知代码存在的问题,然后作出相关优化。
比如,我这里是用到了node-canvas 和 Echart,最后发现这两个对象引用后一直没有内内存回收,导致内存溢出。代码层面做如下优化:
stage.destroy(); chart.dispose(); chart = null; stage = null;
然后,启动服务,模拟1000次请求,结果发现内存占用比之前少了很多,基本上不会出现内存溢出的问题了。