Node.JS

如何在Node.js中检测内存泄漏?

 跟踪Node.js应用程序中的内存泄漏是一个富有挑战性的项目,下面通过使用两个很棒的node模块memwatch 和 heapdump.来说明如何检测跟踪内存泄漏。

下面是一段内存泄漏的样本代码:

var http = require('http');
var server = http.createServer(function (req, res) {
 for (var i=0; i<1000; i++) {
   server.on('request', function leakyfunc() {});
 }
 res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
server.setMaxListeners(0);
console.log('Server running at http://127.0.0.1:1337/. Process PID: ', process.pid);

 

每次请求增加1000个泄漏listener。

我们在linux下通过反复调用脚本:

while true; do curl http://127.0.0.1:1337/; done

在另外一个shell窗口观察node进程:

top -pid <process pid> 

可以看到Node进程内存消耗非常高。

 

内存泄漏检测

 我们通过安装memwatch 能够方便发现内存泄漏,首先安装这个模块:

npm install --save memwatch

在我们的代码中加入:

var memwatch = require('memwatch');
memwatch.setup();

 

加入发现泄漏的事件:

memwatch.on('leak', function(info) {
 console.error('Memory leak detected: ', info);
});

 

现在可以重启应用,会发现有以下输出:

{
 start: Fri Jan 02 2015 10:38:49 GMT+0000 (GMT),
 end: Fri Jan 02 2015 10:38:50 GMT+0000 (GMT),
 growth: 7620560,
 reason: 'heap growth over 5 consecutive GCs (1s) - -2147483648 bytes/hr'
}

 

Memwatch发现了内存泄漏。Memwatch定义泄漏事件是:

当你的heap使用率连续5次垃圾回收后增加表示有一个泄漏事件。

 

内存泄漏分析

 我们需要得到heap的使用率,以及memwatch自己的heap使用偏值,比如如下dump出一个heap:

var hd;
memwatch.on('leak', function(info) {
 console.error(info);
 if (!hd) {
   hd = new memwatch.HeapDiff();
 } else {
   var diff = hd.end();
   console.error(util.inspect(diff, true, null));
   hd = null;
 }
});

 

这段代码将给出更多heap信息:

{ before: {
   nodes: 244023,
   time: Fri Jan 02 2015 12:13:11 GMT+0000 (GMT),
   size_bytes: 22095800,
   size: '21.07 mb' },
 after: {
   nodes: 280028,
   time: Fri Jan 02 2015 12:13:13 GMT+0000 (GMT),
   size_bytes: 24689216,
   size: '23.55 mb' },
 change: {
   size_bytes: 2593416,
   size: '2.47 mb',
   freed_nodes: 388,
   allocated_nodes: 36393,
   details:
   [ { size_bytes: 0,
   '+': 0,
   what: '(Relocatable)',
   '-': 1,
   size: '0 bytes' },
   { size_bytes: 0,
   '+': 1,
   what: 'Arguments',
   '-': 1,
   size: '0 bytes' },
   { size_bytes: 2856,
   '+': 223,
   what: 'Array',
   '-': 201,
   size: '2.79 kb' },
   { size_bytes: 2590272,
   '+': 35987,
   what: 'Closure',
   '-': 11,
   size: '2.47 mb' },
...

 

在泄漏事件之间,看到heap以2.47M大小增加,而罪魁祸首是这里的'Closure',注意到如果你的泄漏是一个特定的类,那么what也许是你的类名,这里有足够信息让你知道泄漏发生哪里了。

虽然我们知道了泄漏发生在Closure,这对于当前案例是适用的,但没有普遍意义上的帮助。

 

Heapdump

node-heapdump是一个能够用于导出V8 heap 的工具,然后在Chrome Devtools中进行详细检测,你也能在其中比较heap快照,这就更容易发现你的泄漏在什么地方。

现在让我们在上面代码中导出堆图,每次有内存泄漏发生时,我们就将V8的stack输出快照到磁盘中:

memwatch.on('leak', function(info) {
 console.error(info);
 var file = '/tmp/myapp-' + process.pid + '-' + Date.now() + '.heapsnapshot';
 heapdump.writeSnapshot(file, function(err){
   if (err) console.error(err);
   else console.error('Wrote snapshot: ' + file);
  });
});

 

重新运行测试,你应该看到文件 '.heapsnapshot'在/tmp目录下,现在到chrome,加载DevTools点击profiles,点击 Load加载我们的快照文件。

你会看到我们的leakyfunc()是真正罪魁祸首:

node内存泄漏发现