Node.js内存泄漏分析

在极客教育出版了一个视频是关于《Node.js 内存泄漏分析》,本文章主要是从内容上介绍如何来处理Node.js内存异常问题。如果希望学习可前往极客学院:

本文章的关键词

- 内存泄漏

- 内存泄漏检测

- GC分析

- memwatch


文章概要

由于内存泄漏在Node.js中非常的常见,可能在浏览器中应用javascript时,对于其内存泄漏不是特别敏感,但作为服务器语言运行时,你就不得不去考虑这些问题。由于很小的逻辑可能导致服务器运行一天或者一个星期甚至一个月才会让你发现内存不断上涨,而终于会到那天你不得不重启服务来保护服务器的性能,那么这种问题就有必要在上线前进行一个系统检测,同时在上线后能够有一个有效的监控程序来保证运行安全。

什么是内存泄漏

在介绍Node.js内存泄漏前,我们应该首先知道什么才是内存泄漏,内存泄漏又包含哪些类型。

内存泄漏概念

内存泄漏也称作“存储渗漏”,用动态存储分配函数,动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束。

上面的定义来自百度百科,当然从上面的定义我们就可以了解到内存泄漏简单说,就是占用着系统资源而一直不释放,所导致的系统资源越来越少,从而导致系统异常的一个比较严重的问题,可以使用下面来解释内存泄漏。

Node.js服务程序假定是一次“班级大扫除”,系统内存资源假定为班级的资源“五个扫把”,而利用资源进行工作的“学生”,这里我们假定为进程。当天学校要进行大扫除,每个班级只有五把扫把,每个人都需要完成一部分扫地工作,学生完成后自动给其他人,当所有人完成扫地工作,大扫除结束,老师首次会将扫把分配给五个人,但是这五个人中存在几个同学手握扫把不做事,即使做完事了,也不会将扫把分配给其他人,导致其他人的扫地工作一直无法进行下去,或者由于扫把有限导致打扫工作进行的很漫长。

内存泄漏类型

内存泄漏包含的类型有:常发性、偶发性、一次性、隐式。

  • 常发性

    发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。

  • 偶发性

    发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。

  • 一次性

    发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。

  • 隐式

    其主要是在调用函数或者模块时,当参数或者输入没有达到界定值时,是不会发生泄漏,当参数或者输入值达到一定时,才会发现内存泄漏,我们称这种为隐式。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天、几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。隐式才是我们本文中所需要去探索,去发现和解决的异常问题。

Node.js内存泄漏会带来的危害

Node.js内存泄漏到底会有哪些危害,既然我们希望去发现和检测内存泄漏,那么我们就必须要首先知道Node.js内存泄漏到底会影响哪些问题。

用户服务异常

一般情况下用户是无法察觉内存泄漏带来的影响,但是对于有些情况下,因为内存泄漏可能导致用户响应很慢,这种情况下对于用户而言无法感受到异常,但是可以普遍感受到服务响应变慢,而且这种情况可能会导致新注册用户丢失等问题。

服务器性能异常

一般情况下,内存泄漏直接的影响就是服务器,服务器会因为内存的不断上涨,从而系统资源可使用的空间越来越小,这样就会慢慢的导致该服务影响到服务器中其他的一些基础服务的运行,从而导致服务器越来越慢,同时导致可使用资源越来越少,直接导致服务器资源耗尽,服务器异常,导致数据丢失等等,比较严重的问题。

常见的 Node.js 内存泄漏问题

这里主要介绍两种关于内存泄漏的代码逻辑,主要是循环引用和无节制循环带来的内存泄漏。

循环引用

这部分在javascript中可能更比较常见,其主要介绍的是说:A对象包含一个指向B的指针,对象B也包含一个指向A的引用。 这就可能造成大量内存得不到回收(内存泄露),因为它们的引用次数永远不可能是 0 ,因此作为回收机制是不会将A和B进行回收的。

var func = function () {}
var el = function () {}
el.func = func;
func.element = el;

例如上面代码中el和func互相引用。而且这种类型的内存泄漏可以说是常发性。

无节制循环

没有对数组有任何限制,并且在数组过大时,没有进行有效的回收处理机制。

模块中的私有方法和属性

任意编写的模块文件中,均会在头和尾部上添加字符串,以形成闭包,然后在require的过程中被调用一次,并且将exports对象存储在内存中,直到进程退出才会回收。这种严格上说并非内存泄漏,只是说需要严格注意模块中的私有变量和方法的使用,避免因为过多的私有变量占用到了过多的系统内存。

假定我们有模块leak.js

var leakArray = [];
exports.leak = function () {
  leakArray.push("leak" + Math.random());
};

那么如果我们创建一个test.js来引用该模块,运行时,则会看的leakArray一直变大。

var Mod = require(‘./leak‘);
Mod.leak();
Mod.leak();
Mod.leak();
Mod.leak();
Mod.leak();

如果这里我们无节制的调用方法leak时,就可能导致数组过大,从而导致一定的问题。

过大的数组循环

先看下如下代码:

for ( var i = 0; i < 100000000; i++ ) {
    var user       = {};
    user.name  = ‘outmem‘;
    user.pass  = ‘123456‘;
    user.email = ‘outmem[@outmem](/user/outmem).com‘;
}

这段代码最主要的原因在于循环太大,直接内存分配到超过v8内存限制数量。由于JavaScript事件循环的执行机制,这段代码没有机会进入下一个事件循环。用setInterval和setTimeout可以进入下一个循环。但是不推荐用setInterval和setTimeout。对于大循环代码,建议最好是分割,然后进行处理,分段进行处理。因为每次都没有效利用好一次循环。一次事件循环,不要超过10ms。

Node.js内存泄漏工具使用实践

这里主要介绍一些常见的Node.js内存泄漏检测工具,并且针对其中的memwatch以及heapdump来进行详细的实践学习。

Node.js内存泄漏工具

node-inspector提供了绑定在Node中的V8分析器和一个基于WebKit Web Inspector的debug界面,大家可以看下这篇博文,其中就是介绍如何应用该工具来检测内存泄漏

http://www.cnblogs.com/ldlchina/p/4762036.html

node-mtrace,它使用了GCC的mtrace工具来分析堆的使用。

https://github.com/Jimbly/node-mtrace

但是该工具提供的是点对点的,如果存在异步函数的话,会比较麻烦,这种可以结合其他工具一起使用会比较方便,大家可以看下github的示例代码

node-heap-dump对V8的堆抓取了一张快照并把所有的东西序列化进一个巨大的JSON文件。它还包含了一些分析研究快照结果的JavaScript工具。这里在memwatch中我们是会应用该工具相应的功能来定位泄漏代码逻辑。

memwatch

是一个专门用来检测和监控内存泄漏的工具,其不仅仅可以在上线前进行扫描监控,也可以在上线后进行有效的内存泄漏检测。

接下来的话,我们就实践应用memwatch来检测内存泄漏的以及通过heapdump抓取GC,进行GC内存分析实践。

memwatch的实践

在学习memwatch之前,首先需要安装配置相应的模块,具体操作可以使用npm install memwatch,下载该模块的时候需要进行编译,因此需要python2.6以上以及需要VS2012以上。如果以上配置遇到问题的时候,可以在我的资料中寻找一篇关于如何解决这两个问题的博文链接http://blog.csdn.net/dan_blog/article/details/50707278

那么接下来我们就来实践应用该模块,首先我们看一下简单的实例代码:

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‘);

从代码本身看其是存在一定的问题,随着用户每一次请求,其内存占用都会提供,我们停止请求一段时间时,其内存也不会降低,说明该段代码存在一定的问题。

大家可以前往极客学院视频地址中下载该代码的源码,其中还包括了其他资料

可以运行下该段代码,如果使用http://127.0.0.1:1337不断的进行访问,如果在windows下可以在进程中查看该进程内存的使用情况

如果你是在Linux的话,可以首先通过命令查看该进程ID,然后再使用top -p 进程ID

ps -ef | grep node
top -p 12202

随时的查看进程所占用的内存,通过访问你会看到其内存的变化情况,同时发现其内存并不会慢慢释放回来。

既然出现了上面的内存泄漏,那么我们就使用memwatch以及heapdump来做检测和分析,改进后的代码如下。

var http = require(‘http‘);
var memwatch = require(‘memwatch‘);
var hd       = new memwatch.HeapDiff();
var heapdump = require(‘heapdump‘);
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‘);
memwatch.on(‘leak‘, function(info) {
    var diff = hd.end();
    console.log(JSON.stringify(diff));

    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);
    });
});
server.setMaxListeners(0);
console.log(‘Server running at http://127.0.0.1:1337/. Process PID: ‘, process.pid);

上面的逻辑中包含了使用memwatch来检测内存泄漏,同时还包含了使用heapdump来抓取内存的实时情况,通过运行如上代码,然后使用压测工具对http://127.0.0.1:1337进行压测,当压测到一定的情况后,在运行窗口你可以看的其内存泄漏的提醒,并在这时候会在file这个目录文件中(如果在windows中最好就修改下file这个文件路径,这里的示例代码是相对Linux环境的)记下当前内存泄漏时的内存GC情况。

GC内存分析

既然获取到该文件后,我们再使用google chrome的工具来分析GC的内存情况,并查看出导致内存泄漏的原因。

打开工具后,再使用Load,将我们刚生成的文件加载进去。

加载进去以后关于分析方法,大家可以参考该文章

http://itindex.net/detail/52929-chrome-%E5%BC%80%E5%8F%91-%E5%B7%A5%E5%85%B7

里面有详情的介绍,当然如果想了解更具体的分析方法,大家可以去极客学院上查看本课程的视频学习资料。

总结

这就是本文所介绍的知识,在看完本文以后,大家至少了解什么是内存泄漏,Node.js的内存泄漏会导致哪些问题,以及如何应用memwatch和heapdump来检测和分析内存泄漏问题,同时需要简单了解Chrome中的内存分析工具的使用。

时间: 2024-10-25 10:29:04

Node.js内存泄漏分析的相关文章

Javascript的内存泄漏分析

作为程序员(更高大尚的称谓:研软件研发)的我们,无论是用Javascript,还是.net, java语言,肯定都遇到过内存泄漏的问题.只不过他们都有GC机制来帮助程序员完成内存回收的事情,如果你是C++开发者(你懂的).....,如果你是前端开发者,肯定在使用Javascript(你或者会说,Js是世界上最棒的语言),但我这里也得告诉你,Js的内存泄漏会来得更为突然,或者让你都无法察觉.本文就带大家领略一下Js的风骚: 一.模块化引起的内存泄漏 代码如下: // module date.js

View的post方法导致的内存泄漏分析

简述: 写这篇文章的缘由是最近项目中查内存泄漏时,发现最终原因是由于异步线程调用View的的post方法导致的. 为何我会使用异步线程调用View的post方法,是因为项目中需要用到很多复杂的自定义布局,需要提前解析进入内存,防止在主线程解析导致卡顿,具体的实现方法是在Application启动的时候,使用异步线程解析这些布局,等需要使用的时候直接从内存中拿来用. 造成内存泄漏的原因,需要先分析View的post方法执行流程,也就是文章前半部分的内容 文章内容: View#post方法作用以及实

android 内存泄漏分析技巧

java虚拟机运行一般都有一个内存界限,超过这个界限,就会报outofmemory.这个时候一般都是存在内存泄漏.解决内存泄漏问题,窃以为分为两个步骤:分析应用程序是否真的有内存泄漏,找到内存泄漏的地方.这两个步骤都不是一般意义上的调试,直接打log,断点调试都不是太给力.动脑筋想一想,内存问题应该在很多地方上都会出现,这么常见的问题应该是有工具的.android现在更可以说是一个生态系统,当然也有很多开发辅助工具.在前面的两个步骤中都有很强大的武器,熟练的掌握这些利器,分析问题就会事半功倍.

使用Eclipse Memory Analyzer进行内存泄漏分析三部曲

源地址:http://seanhe.iteye.com/blog/898277 一.准备工作 分析较大的dump文件(根据我自己的经验2G以上的dump文件就需要使用以下介绍的方法,不然mat会出现oom)需要调整虚拟机参数 找个64位的系统在MemoryAnalyzer.ini设置-Xmx2g 如果是32位的xp可以使用下面的方法进行尝试: 安装jrockit 6.0的JDK mat使用jrockit的jdk来启动 Java代码   -vm D:/Program Files/Java/jroc

Java内存泄漏分析与解决方案

Java内存泄漏是每个Java程序员都会遇到的问题,程序在本地运行一切正常,可是布署到远端就会出现内存无限制的增长,最后系统瘫痪,那么如何最快最好的检测程序的稳定性,防止系统崩盘,作者用自已的亲身经历与各位网友分享解决这些问题的办法. 作为Internet最流行的编程语言之一,Java现正非常流行.我们的网络应用程序就主要采用Java语言开发,大体上分为客户端.服务器和数据库三个层次.在进入测试过程中,我们发现有一个程序模块系统内存和CPU资源消耗急剧增加,持续增长到出现java.lang.Ou

100%正确的内存泄漏分析工具 &#160; &#160; &nbsp; --------tMemMonitor (TMM)

C/C++由于灵活.高效的优点一直以来都是主流的程序设计语言之一,但是其内存的分配与释放均由程序员自己管理,当由于疏忽或错误造成程序未能释放不再使用的内存时就会造成内存泄漏.在大型.复杂的应用程序中,内存泄漏往往是最常见的问题,因而及时解决内存泄漏非常必要.tMemMonitor (TMM)作为一个专业.准确.易用的内存泄漏分析工具,可以帮助C/C++程序员迅速地解决内存泄漏这个令人头疼的问题. TMM下载地址: 一.开发背景 目前市面上已有一些Windows平台下的内存泄漏动态检测工具,比如U

Android内存泄漏分析实战

内存泄漏简单介绍 java能够保证当没有引用指向对象的时候,对象会被垃圾回收器回收.与c语言自己申请的内存自己释放相比,java程序猿轻松了非常多.可是并不代表java程序猿不用操心内存泄漏.当java程序发生内存泄漏的时候往往具有隐蔽性.因此要借助一些专业的平台资源去保证安全性,比如能够通过加密实现. 定义 引用百度百科的定义:"用动态存储分配函数动态开辟的空间,在使用完成后未释放,结果导致一直占领该内存单元. 直到程序结束".从程序员的角度来看"内存泄漏",事实

内存泄漏分析工具tMemMonitor (TMM)使用简介

C/C++由于灵活.高效的优点一直以来都是主流的程序设计语言之一,但是其内存的分配与释放均由程序员自己管理,当由于疏忽或错误造成程序未能释放不再使用的内存时就会造成内存泄漏.在大型.复杂的应用程序中,内存泄漏往往是最常见的问题,因而及时解决内存泄漏非常必要.tMemMonitor (TMM)作为一个专业.准确.易用的内存泄漏分析工具,可以帮助C/C++程序员迅速地解决内存泄漏这个令人头疼的问题. TMM下载地址:http://download.csdn.net/download/tmemmoni

Android内存泄漏分析及调试

尊重原创作者,转载请注明出处: http://blog.csdn.net/gemmem/article/details/13017999 此文承接我的另一篇文章:Android进程的内存管理分析 首先了解一下dalvik的Garbage Collection: 如上图所示,GC会选择一些它了解还存活的对象作为内存遍历的根节点(GC Roots),比方说thread stack中的变量,JNI中的全局变量,zygote中的对象(class loader加载)等,然后开始对heap进行遍历.到最后,