标签归档:内存泄露

Node.js 内存泄露

#监控日志输出
Process: heapTotal 606.50 MB heapUsed 604.66 MB rss 642.38 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 604.72 MB rss 642.36 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 604.73 MB rss 642.36 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.01 MB rss 642.71 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.03 MB rss 642.71 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.64 MB rss 643.43 MB
-----------------------------------------------------------
Process: heapTotal 607.48 MB heapUsed 605.68 MB rss 643.43 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.70 MB rss 643.39 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.72 MB rss 643.39 MB
-----------------------------------------------------------
Process: heapTotal 608.47 MB heapUsed 605.82 MB rss 643.56 MB
-----------------------------------------------------------

可以看到,RSS一直不断变大,主要原因是堆内存持续增加。在Node中,堆内存是V8管理的,那么必定是某变量无法被回收导致。

代码如下:

var config = require('./config');

var http = require('http');
var cheerio = require('cheerio');

// 如果没有输出则list为空数组
var parseHtml = function(html, itemid) {
	$ = cheerio.load(html);
	// ...
};

var showMem = function () {
        var mem = process.memoryUsage();
        var format = function (bytes) {
            return (bytes / 1024 / 1024).toFixed(2) + ' MB';
        };

        console.log('Process: heapTotal ' + format(mem.heapTotal) + ' heapUsed ' + format(mem.heapUsed) + ' rss ' + format(mem.rss));
        console.log('-----------------------------------------------------------');
};

var server = http.createServer(function (req, res) {
	//showMem();
        //调用parseHtml 
});

Http内部调用parseHtml, 所以问题必定出现在parseHtml()函数内,经过一番测试最终发现问题出现在$ = cheerio.load(html)这个语句,由于没有使用var定义变量,导致每次调用都有一份变量挂到global.$上,在进程周期内,全局变量无法被垃圾回收器回收,所以堆内存持续增长。

把var添加到$前,内存终于开始回收了。从测试来看,内存上涨,然后在某个点下降一截,故而可以推测,V8的垃圾回收器视乎不太勤快。

对于小内存机器,V8的垃圾回收内存太慢,短时间过多并发也会导致大量内存占用,为了保护系统安全,有必要设置一个阀值,一旦超越则重启进程(重启必定所有内存均被回收):

#!/bin/bash

live=`ps -efH | grep 'agent.js' | grep -v 'grep' | wc -l`
if [ $live -eq 0 ]; then
	/usr/local/bin/node /var/www/ebay-ap/agent.js 2>&1 >> /var/www/ebay-ap/log.txt &
fi

pid=`ps aux | grep 'agent.js' | grep -v grep | awk '{print $2}'`
rss=`ps aux | grep 'agent.js' | grep -v grep | awk '{print $6}'`

threshold=204800

if [ $rss -gt $threshold ]; then
	cmm="/usr/bin/kill"
	if [ ! -f "$cmm" ]; then
		cmm="/bin/kill"
	fi
        kill=`$cmm -9 $pid`
        if [ $kill ]; then
                echo PID: ${pid} Kill Failed [RSS: ${rss} gt ${threshold}]
        else
                echo PID: ${pid} Kill Success [RSS: ${rss} gt ${threshold}]
		/usr/local/bin/node /var/www/node/agent.js 2>&1 >> /var/www/node.log.txt &
        fi
fi

这段SHELL脚本作用为:
1 如果进程异常退出,自动拉起进程
2 如果进程内存占用超过了阀值,则先杀掉进程,然后启动进程

内存超越阀值,重启进程不但可以保护系统,同时也可以有效防止内存泄露。