node内存溢出追查

2016年8月22日

背景

关于内存泄露,其实在浏览器中javascript一直很少需要对内存控制的那么精细,因为基本等不到内存占用过多,用户可能就已经关了浏览器了,即使内存泄露过多,影响的也只是当前用户的终端。但nodejs就不同了,它是运行在服务器上的,所有的用户请求都在这上面运行,使得对内存的管理需要变得更加敏感。即使有一点点的内存泄露也会累加,导致内存占用不断增多,最终内存会爆掉,导致进程异常退出。

V8引擎内存管理特点

Javascript的特点就是像Java一样,由垃圾回收机制进程自动的内存管理,而不用像C/C++程序员那样需要手动关注内存的分配和释放。node是运行在V8引擎上的,相当于java的虚拟机。在V8引擎中使用内存是有限制的,64位系统下约为1.4GB,32位系统下约为0.7GB。所以当内存超过它的限制时,就容易导致异常,进程中断。

一次node内存泄露追查

在看日志的时候发现有异常,提示最后异常时候的垃圾回收日志情况,可以看出内存基本都到了1.4G,由于我机器是64位的,所以就像上面说的,v8引擎在64位系统下能用的内存上限约为1.4GB,所以这里的异常极有可能是内存爆了。
日志异常
日志异常
此时再看看整个机器的内存状态日志,发现内存状态很不正常,内存持续上升,又突然下降,基本可以推断出来是内存持续积累,然后超出限制,导致进程退出,释放内存,重启进程,继续积累内存,不断循环。
内存异常
这样基本可以肯定是node程序有内存溢出了,接下来要做的就是找出内存泄露点,解决问题。

使用工具heapdump

这里我们选择使用heapdump来抓取内存快照来分析具体内存是在哪里占用的比较多,主动通过ab压测后抓取快照,获得heapsnapshot文件。
heapdump
再在chrome中查看快照发现很多图片的image内存没被释放
profile-heap
之后查看代码中Image中的相关模块

var img = new Image;
img.onload = function () {
    img.onload = null;
    delete img.onload;
}
img.onerror = function () {
}

发现onerror是后来加的,没给加上onerror事件的解绑动作。非常有可能是这里导致image对象一直不能被释放。然后继续修改代码,如下:

var img = new Image;
img.onload = function () {
    img.onload = null;
    delete img.onload;
    img.onerror = null;
    delete img.onerror;
}
img.onerror = function () {
    img.onload = null;
    delete img.onload;
    img.onerror = null;
    delete img.onerror;
}

加上了事件解绑代码后,再用ab进行压测,发现这时系统的内存已经趋于稳定,不再持续累加增长了。