nodejs应用内存泄漏分析过程
nodejs应用部署完毕后,需要实时监控应用的情况,除了应用的业务问题,内存泄漏以及导致的性能问题也需要关注,本文为分析的整体流程做下梳理
如何确定部署机器及该应用的内存使用情况
一、 机器内存使用
查询内存的使用情况在windows或者mac电脑上都会有进程/任务管理器,打开就可以看到具体的使用情况,而在linux上需要通过 free
或者 top
命令操作即可
xxx@xxxxx:~# free
total used free shared buff/cache available
Mem: 4039456 1669408 1110168 3024 1259880 2102916
Swap: 969964 0 969964
由于一台机器上可能存在多个应用同时在运行,我们需要查看到具体某一个应用的内存使用情况,如下通过指定pid来查看具体进程的相关运行情况
xxx@xxx:~# top -p 19341
top - 12:06:23 up 76 days, 1:23, 1 user, load average: 0.00, 0.00, 0.00
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.5 us, 0.2 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4039456 total, 1109976 free, 1669616 used, 1259864 buff/cache
KiB Swap: 969964 total, 969964 free, 0 used. 2102708 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
193** user 20 0 9465** 491** 209** S 0.0 1.2 0:07.12 node
对于企业来说都有自己的监控平台,不需要自己登录机器去操作,只需要在启动服务的机器上启动一个额外的监控服务,通过轮询请求等操作获取机器上的应用运行情况,比如守护进程等等....
以上是对于机器的内存使用等情况的监控,对于应用的运行情况甚至某段代码运行后的内存变化等情况的监控,如果针对内存的变化监控,可以简单使用process.memoryUsage()
或者 os
模块获取内存使用情况来判断是否内存溢出
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log('someMessage');
}
};
};
app.get('/leak', (ctx) => {
let beforeMemoryInfo = process.memoryUsage();
replaceThing();
let memoryInfo = process.memoryUsage();
ctx.body = {before: beforeMemoryInfo, after: memoryInfo};
})
返回结果类似
{
rss: 4935680, // 表示 Resident Set 的大小
heapTotal: 1826816, // 表示堆的总大小
heapUsed: 650472, // 表示堆的实际使用大小
external: 49879
}
当持续请求 /leak
接口时,发现返回的结果heapUsed持续增加时就意味着内存未释放,会发生内存溢出
以上是针对具体代码的运行情况监控,但是对于线上应用,内存溢出总是让人难以排查,只能根据整体情况来进行深入分析,我们来模拟线上运行的情况来监控运行时内存使用情况,可以使用对应的模块来截取当前运行时的快照,然后进行分析,对于nodejs可以使用heapdump模块截取快照
var heapdump = require('heapdump');
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log('someMessage');
}
};
};
app.get('/leak', (ctx) => {
replaceThing();
ctx.body = 'ok';
});
app.listen(3000);
启动服务之后,可用通过以下两种方式记录快照
- 在指定代码调用API记录快照,都是在运行
replaceThing
运行前后
heapdump.writeSnapshot('/var/local/' + Date.now() + '.heapsnapshot');
- 在UNIX 平台上,可以使用命令行发送指令记录快照,按照以上代码,可以在
/leak
接口调用前后执行两次该命令,默认在引用heapdump
模块的文件目录下生成.snapshot
文件
kill -USR2 <pid>
通过以上的操作,可以得到2份快照文件,建议是在运行前
、运行少量次数
、运行多次
这三种情况下记录快照,这样对比起来比较好分析
注意,记录快照写入文件是同步操作,会阻塞程序运行,所以尽量在流量少时操作
如何分析
借助chrome的dev-tool,可以把记录的snapshot文件导入进行分析,如下图
分析上图几个方面
- 左边Profiles snapshot问价的大小,如果前后相差太大,且后续快照没有平缓的迹象,基本判断内存溢出
- Distance表示从跟对象
global
对改对象的路径层级,一般情况下distance过大,就意味着嵌套层级太深且可能出现循环引用问题 - Shallow SIze 表示改对象本身所占内存大小
- Retained Size 表示该对象本身已经其所引用的对象的总大小,意味着只要将其回收,就可以释放 Retained Size大小的内存
以上代码由于闭包问题,出现循环引用导致内存溢出,上图的引用嵌套只是一部分,具体可以自己试试,至于以上代码循环引用的问题,可以参考这篇文章An interesting kind of JavaScript memory leak;
chrome dev-tool的更多使用方式,可以参考以下文章
小手抖一抖,给我点个赞
0条评论