nodeJs内存泄漏问题详解


Posted in NodeJs onSeptember 05, 2016

之前一次偶然机会发现,react 在server渲染时,当NODE_ENV != production时,会导致内存泄漏。具体issues: https://github.com/facebook/react/issues/7406 。随着node,react同构等技术地广泛运用,node端内存泄漏等问题应该引起我们的重视。为什么node容易出现内存泄漏以及出现之后应该如何排查,下面通过一个简单的介绍以及例子来说明。

首先,node是基于v8引擎基础上,其内存管理方式与v8一致。下面简单介绍v8的相关内存特效。

V8内存限制

node基于V8构建,通过V8的方式进行分配跟管理js对象。V8对内存的使用有限制(老生代内存64位系统下约为1.4G,32位系统下约为0.7G,新生代内存64位系统下约为32MB,32系统下约为16MB)。在这样的限制下,将导致无法操作大内存对象。如果不小心触碰这个界限,就会造成进程退出。

原因:V8在执行垃圾回收时会阻塞JavaScript应用逻辑,直到垃圾回收结束再重新执行JavaScript应用逻辑,这种行为被称为“全停顿”(stop-the-world)。若V8的堆内存为1.5GB,V8做一次小的垃圾回收需要50ms以上,做一次非增量式的垃圾回收甚至要1秒以上。

通过node --max-old-space-size=xxx(单位MB) , node --max-new-space-size=xxx(单位KB) 设置新生代内存以及老生代内存来破解默认的内存限制。

V8的堆构成

V8的堆其实并不只是由老生代和新生代两部分构成,可以将堆分为几个不同的区域:

  1. 新生代内存区:大多数的对象被分配在这里,这个区域很小但是垃圾回特别频繁
  2. 老生代指针区:属于老生代,这里包含了大多数可能存在指向其他对象的指针的对象,大多数从新生代晋升的对象会被移动到这里
  3. 老生代数据区:属于老生代,这里只保存原始数据对象,这些对象没有指向其他对象的指针
  4. 大对象区:这里存放体积超越其他区大小的对象,每个对象有自己的内存,垃圾回收其不会移动大对象
  5. 代码区:代码对象,也就是包含JIT之后指令的对象,会被分配在这里。唯一拥有执行权限的内存区
  6. Cell区、属性Cell区、Map区:存放Cell、属性Cell和Map,每个区域都是存放相同大小的元素,结构简单

GC回收类型

增量式GC

表示垃圾回收器在扫描内存空间时是否收集(增加)垃圾并在扫描周期结束时清空垃圾。

非增量式GC

使用非增量式垃圾收集器时,一收集到垃圾即将其清空。

垃圾回收器只会针对新生代内存区、老生代指针区以及老生代数据区进行垃圾回收。对象首先进入占用空间较少的新生代内存。大部分对象会很快失效,非增量GC直接回收这些少量内存。假如有些对象一段时间内不能被回收,则进去老生代内存区。这个区域则执行不频繁的增量GC,且耗时较长。

那什么时候才会导致内存泄漏的发生呢?

内存泄漏的途径

  1. 内存泄露
  2. 缓存
  3. 队列消费不及时
  4. 作用域未释放

Node的内存构成主要是通过V8进行分配的部分和Node自行分配的部分。受V8的垃圾回收限制的主要是V8的堆内存。造成内存泄漏的主要原因:1,缓存;2,队列消费不及时;3,作用域未释放

内存泄漏分析

查看V8内存使用情况(单位byte)

process.memoryUsage(); 
  {
    ress: 47038464,  
    heapTotal: 34264656,  
    heapUsed: 2052866  
  }

ress:进程的常驻内存部分

heapTotal,heapUsed:V8堆内存信息

查看系统内存使用情况(单位byte)

os.totalmem()
os.freemem()

返回系统总内存以及闲置内存

查看垃圾回收日志

node --trace_gc -e "var a = []; for( var i = 0; i < 1000000; i++ ) { a.push(new Array(100)); }" >> gc.log  //输出垃圾回收日志

node --prof //输出node执行时性能日志。 使用windows-tick.processor查看。

分析监控工具

v8-profiler 对v8堆内存抓取快照和对cpu进行分析
node-heapdump 对v8堆内存抓取快照
node-mtrace 分析堆栈使用
node-memwatch 监听垃圾回收情况

node-memwatch

memwatch.on('stats',function(info){
  console.log(info)
})
memwatch.on('leak',function(info){
  console.log(info)
})

stats事件:每次进行全堆垃圾回收时,将触发一次stats事件。这个事件将会传递内存统计信息。

{
"num_full_gc": 17, //第几次全栈垃圾回收
"num_inc_gc": 8,  //第几次增量垃圾回收
"heap_compactions": 8, //第几次对老生代进行整理
"estimated_base": 2592568, //预估基数
"current_base": 2592568, //当前基数
"min": 2499912, //最小
"max": 2592568, //最大 
"usage_trend": 0 //使用趋势
  }

观察num_full_gc和num_inc_gc反映垃圾回收情况。

leak事件:如果经过连续5次垃圾回收后,内存仍然没有被释放,意味着内存泄漏的发生。这个时候会触发一个leak事件。

{ start: Fri, 29 Jun 2012 14:12:13 GMT,
end: Fri, 29 Jun 2012 14:12:33 GMT,
growth: 67984,
reason: 'heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr'
}

Heap Diffing 堆内存比较 排查内存溢出代码。
下面,我们通过一个例子来演示如何排查定位内存泄漏:

首先我们创建一个导致内存泄漏的例子:

//app.js
var app = require('express')();
var http = require('http').Server(app);
var heapdump = require('heapdump');

var leakobjs = [];
function LeakClass(){
  this.x = 1;
}

app.get('/', function(req, res){
  console.log('get /');
  for(var i = 0; i < 1000; i++){
    leakobjs.push(new LeakClass());
  }
  res.send('<h1>Hello world</h1>');
});

setInterval(function(){
  heapdump.writeSnapshot('./' + Date.now() + '.heapsnapshot');
}, 3000);

http.listen(3000, function(){
  console.log('listening on port 3000');
});

这里我们通过设置一个不断增加且不回被回收的数组,来模拟内存泄漏。

通过使用heap-dump模块来定时纪录内存快照,并通过chrome开发者工具profiles来导入快照,对比分析。

我们可以看到,在浏览器访问 localhost:3000 ,并多次刷新后,快照的大小一直在增长,且即使不请求,也没有减小,说明已经发生了泄漏。

nodeJs内存泄漏问题详解

接着我们通过过chrome开发者工具profiles, 导入快照。通过设置comparison,对比初始快照,发送请求,平稳,再发送请求这3个阶段的内存快照。可以发现右侧new中LeakClass一直增加。在delta中始终为正数,说明并没有被回收。

nodeJs内存泄漏问题详解

小结

针对内存泄漏可以采用植入memwatch,或者定时上报process.memoryUsage内存使用率到monitor,并设置告警阀值进行监控。

当发现内存泄漏问题时,若允许情况下,可以在本地运行node-heapdump,使用定时生成内存快照。并把快照通过chrome Profiles分析泄漏原因。若无法本地调试,在测试服务器上使用v8-profiler输出内存快照比较分析json(需要代码侵入)。

需要考虑在什么情况下开启memwatch/heapdump。考虑heapdump的频度以免耗尽了CPU。 也可以考虑其他的方式来检测内存的增长,比如直接监控process.memoryUsage()。

当心误判,短暂的内存使用峰值表现得很像是内存泄漏。如果你的app突然要占用大量的CPU和内存,处理时间可能会跨越数个垃圾回收周期,那样的话memwatch很有可能将之误判为内存泄漏。但是,这种情况下,一旦你的app使用完这些资源,内存消耗就会降回正常的水平。所以需要注意的是持续报告的内存泄漏,而可以忽略一两次突发的警报。

NodeJs 相关文章推荐
Nodejs异步回调的优雅处理方法
Sep 25 NodeJs
nodejs简单实现操作arduino
Sep 25 NodeJs
Nodejs下用submit提交表单提示cannot post错误的解决方法
Nov 21 NodeJs
nodejs基础应用
Feb 03 NodeJs
NodeJS测试框架mocha入门教程
Mar 28 NodeJs
详解使用nodeJs安装Vue-cli
May 17 NodeJs
Nodejs实现文件上传的示例代码
Sep 26 NodeJs
Nodejs中crypto模块的安全知识讲解
Jan 03 NodeJs
原生nodejs使用websocket代码分享
Apr 07 NodeJs
nodejs同步调用获取mysql数据时遇到的大坑
Mar 02 NodeJs
Nodejs 识别图片类型的方法
Aug 15 NodeJs
NodeJS开发人员常见五个错误理解
Oct 14 NodeJs
浅谈Nodejs应用主文件index.js
Aug 28 #NodeJs
NodeJS远程代码执行
Aug 28 #NodeJs
用NodeJS实现批量查询地理位置的经纬度接口
Aug 16 #NodeJs
使用nodejs中httpProxy代理时候出现404异常的解决方法
Aug 15 #NodeJs
NodeJs的优势和适合开发的程序
Aug 14 #NodeJs
在windows上用nodejs搭建静态文件服务器的简单方法
Aug 11 #NodeJs
Nodejs抓取html页面内容(推荐)
Aug 11 #NodeJs
You might like
PHP数组函数array_multisort()用法实例分析
2016/04/02 PHP
Laravel框架模板加载,分配变量及简单路由功能示例
2018/06/11 PHP
PHP时间处理类操作示例
2018/09/05 PHP
js 发个判断字符串是否为符合标准的函数
2009/04/27 Javascript
jQuery Deferred和Promise创建响应式应用程序详细介绍
2013/03/05 Javascript
javascript 3d 逐侦产品展示(核心精简)
2014/03/26 Javascript
js实现发送验证码后的倒计时功能
2015/05/28 Javascript
Bootstrap的图片轮播示例代码
2015/08/31 Javascript
JavaScript实现自动生成网页元素功能(按钮、文本等)
2015/11/21 Javascript
javascript数据结构之双链表插入排序实例详解
2015/11/25 Javascript
JS命令模式例子之菜单程序
2016/10/10 Javascript
浅谈js的异步执行
2016/10/18 Javascript
canvas绘制表盘时钟
2017/01/23 Javascript
JavaScript数组常用的增删改查与其他属性详解
2020/10/13 Javascript
Vue+penlayers实现多边形绘制及展示
2020/12/24 Vue.js
[01:13:46]iG vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.16
2018/08/17 DOTA
python处理中文编码和判断编码示例
2014/02/26 Python
Python处理json字符串转化为字典的简单实现
2016/07/07 Python
Python 给某个文件名添加时间戳的方法
2018/10/16 Python
python去重,一个由dict组成的list的去重示例
2019/01/21 Python
python 读取dicom文件,生成info.txt和raw文件的方法
2019/01/24 Python
Flask项目中实现短信验证码和邮箱验证码功能
2019/12/05 Python
python使用OpenCV模块实现图像的融合示例代码
2020/04/10 Python
详解CSS3中nth-child与nth-of-type的区别
2017/01/05 HTML / CSS
利用CSS3的3D效果制作正方体
2020/03/10 HTML / CSS
英国排名第一的礼品体验公司:Red Letter Days
2018/08/16 全球购物
美国滑板店:Tactics
2020/11/08 全球购物
保安的辞职报告怎么写
2014/01/20 职场文书
营销与策划专业求职信
2014/06/20 职场文书
广告设计专业毕业生自我鉴定
2014/09/27 职场文书
工伤事故证明
2014/10/20 职场文书
2014年合同管理工作总结
2014/12/02 职场文书
幼儿园六一儿童节活动总结
2015/02/10 职场文书
小学运动会加油词
2015/07/18 职场文书
辞职报告(范文三篇)
2019/08/27 职场文书
Pytorch 中net.train 和 net.eval的使用说明
2021/05/22 Python