内存泄漏(MemoryLeak)是指由于疏忽或错误导致程序未能释放不再使用的内存的情况。如果内存泄漏的位置很关键,那么随着处理的进行,可能会持有越来越多的无用内存,这会导致服务器响应变慢,严重时内存会达到一定的极限(可能是进程的上限,如v8的上限;也可能是系统可用内存的上限),会使应用程序崩溃。传统C/C++存在野指针,对象用完不释放导致的内存泄漏。但是在Java、JavaScript等虚拟机执行的语言中,由于采用了GC(GarbageCollection,垃圾回收)机制自动释放内存,大大解放了程序员的精力,不需要像传统语言一样不断地处理记忆。被他的释放吓坏了。但是,即使有可以自动释放的GC机制,也不代表不存在内存泄露的问题。内存泄漏仍然是开发者无法绕过的问题。今天让我们学习如何分析Node.js中的内存泄漏。Node.js中的GCNode.js使用V8作为JavaScript执行引擎,所以讨论Node.js的GC就等同于讨论V8的GC。V8中一个对象的内存是否释放取决于程序中是否还有对该对象的引用。在V8中,每次GC时,对象的引用会按照根对象(浏览器环境下是window,Node.js环境下是global)依次排序。如果可以从根的引用链访问,V8会标记为可达对象,反之则标记为不可达对象。被标记为不可达对象(即没有引用的对象)后,会被V8回收。更多细节参见alinode对V8GC的解读。了解了以上几点,你就会知道Node.js内存泄漏的原因是本该清除的对象被可达对象引用了,却没有正确清除,留在内存中。内存泄漏的几种情况1.全局变量a=10;//没有声明对象。global.b=11;//引用全局变量的原因很简单,全局变量直接链接到根对象,不会被清除。2.闭包函数out(){constbigData=newBuffer(100);inner=function(){voidbigData;}}闭包将引用父函数中的变量。如果不释放闭包,就会造成内存泄漏。上面的例子中,inner直接挂在了root上,所以out函数每次执行产生的bigData不会被释放,导致内存泄漏。需要注意的是,这里给出的例子只是将引用挂在了全局对象上。实际业务情况可能是挂在一个可以追根溯源的对象上造成的。3、事件监听Node.js事件监听也可能会导致内存泄露。比如重复监听同一个事件而忘记移除(removeListener),就会造成内存泄漏。这种情况在给多路复用对象添加事件时很容易出现,所以重复事件监听可能会收到如下警告:(node:2752)Warning:PossibleEventEmittermemoryleakdetected。11个哈哈听众加了。使用发射器。setMaxListeners()增加limit例如Node.js中Agent的keepAlive为true时,可能会导致内存泄漏。当AgentkeepAlive为true时,之前使用过的socket会被重新使用。如果给socket添加了事件监听而忘记清除,socket的多路复用会导致重复事件监听和内存泄漏。原理上和添加事件监听时忘记清除是一样的。在使用Node.js的http模块时,不用keepAlive复用是没有问题的。重用后,可能会发生内存泄漏。因此,需要了解被添加事件监听器的对象的生命周期,自行移除时要小心。这个问题的例子可以看Github上的issues(nodeAgentkeepAlivememoryleak)4.其他原因还有一些其他情况可能会导致内存泄漏,比如cache。使用缓存时,必须知道缓存了多少对象。如果缓存对象过多,则必须限制缓存对象的数量。另外,大量消耗CPU的代码也会造成内存泄漏。服务器运行时,如果有高CPU同步代码,由于Node.js是单线程,无法处理请求,请求堆积导致内存占用过高。.定位内存泄漏1.重现内存泄漏如果要定位内存泄漏,通常有两种情况:对于只要正常使用就可以重现的内存泄漏,这是一种很简单的情况,只要模拟一下就可以了在测试环境中,可以检查它们。.对于偶尔的内存泄漏,通常与特殊输入有关。这种输入的稳定再现是一个耗时的过程。如果无法通过代码日志定位到这个特殊输入,建议在生产环境打印内存快照。需要注意的是,打印内存快照属于CPU密集型操作,可能会影响线上业务。快照工具推荐使用heapdump保存内存快照,使用devtool查看内存快照。使用heapdump保存内存快照时,只会有Node.js环境中的对象没有干扰(如果使用node-inspector,快照中会有前端变量干扰)。PS:在某些Node.js版本上安装heapdump可能会出错,建议使用npminstallheapdump-target=Node.js版本安装。2.打印内存快照在代码中引入heapdump,使用heapdump.writeSnapshot打印内存快照。为了减少普通变量的干扰,可以在打印内存快照前调用主动释放内存的gc()函数(启动时添加–expose-gc参数启用)。constheapdump=require('heapdump');constsave=function(){gc();打印行上代码的heapdump.writeSnapshot('./'+Date.now()+'.heapsnapshot');}此时建议根据内存增长打印快照。heapdump可以使用kill信号向程序打印内存快照(仅在*nix系统上可用)。kill-USR2
