最近关注了一个国外的技术博客RisingStack,里面有很多对新手来说优质友好的文章。正好最近在学习Node.js的各种实现原理,这里大胆翻译一个Node.js的垃圾回收机制(原文链接)。正文在本文中,您将了解Node.js的垃圾收集(garbegecollection)机制是如何工作的;也就是当你敲代码的时候,后台是如何帮你清空内存中的垃圾的。1.Node.js应用的内存管理合理分配内存对所有应用来说都是至关重要的。内存管理的任务是在程序申请内存时为程序动态分配内存块;并在程序不再需要时释放内存。应用级内存管理有两种模式:手动管理和自动管理。自动管理机制通常包括垃圾收集机制。下面的代码片段展示了C语言中如何分配内存,使用手动内存管理:下面的代码片段展示了C语言中的内存分配,属于手动管理:h>#includeintmain(){字符名称[20];字符*描述;strcpy(名称,"RisingStack");//内存分配描述=malloc(30*sizeof(char));if(description==NULL){fprintf(stderr,"Error-unabletoallocaterequiredmemory\n");}else{strcpy(description,"TracebyRisingStack是一个APM。");}printf("公司名称=%s\n",name);printf("描述:%s\n",描述);//释放内存free(description);}在手动内存管理机制中,释放无用内存的任务落在了程序员身上。这可能会给应用程序带来严重的问题:内存泄漏:有可能一些占用的内存没有被释放。当一个对象被删除(过早释放)时,可能会有一些指针没有指向任何有效的对象,但仍然指向原来的内存。这种指针称为“悬挂指针”。如果此时再使用这块内存,就会出现严重的安全问题。但幸运的是,Node.js有自己的垃圾回收机制,所以你不需要手动管理内存。2、垃圾收集机制的概念垃圾收集是一种自动管理应用程序占用内存的机制,简称“GC”(为方便起见,本文使用此缩写)。它的任务是回收无用对象(即垃圾)占用的内存。它于1959年首次出现在LISP语言中,由JohnMcCarthy发明。GC判断一个对象是否为垃圾的标准是:是否有其他对象引用它。GC知道对象不再使用的方式是没有其他对象引用它们。如果没有GC,下图是没有垃圾管理机制时的内存情况。可以看出有些对象和其他对象之间没有引用关系,但是它们的内存不会被回收。有了GCGC后,没有引用关系的对象占用的内存会被GC悄悄回收。使用GC的优点它可以防止野指针/悬挂指针错误,它不会尝试释放已经释放的空间,它会保护您免受某些类型的内存泄漏。当然,使用垃圾收集器并不能解决你所有的问题,它也不是内存管理的灵丹妙药。让我们来看看你应该记住的事情!避免悬挂指针。它不会尝试重复释放尚未使用的内存。它将防止某些类型的内存泄漏。当然GC并不能解决所有内存相关的问题,它不是内存管理问题的万能药。使用GC的一些注意事项仍然需要开发人员牢记:性能影响-为了决定可以释放什么,GC会消耗计算能力不可预测的停顿-现代GC实现试图避免“stop-the-world”收集对性能的影响:在判断释放哪块内存时,GC会占用CPU资源。不可预测的中断:虽然现在的GC避免了“stopeverything”的情况,但这是不可避免的。译注:“stop-the-world”的意思是当垃圾回收还没有结束时,内存不会响应外部请求,直到回收完成后应用程序才会继续响应请求。3.Node.jsGarbageCollection&MemoryManagementPractice学代码就是写代码。下面是几段代码来展示本节的主题。首先介绍几个基本概念:栈(Stack)栈中存放的是局部变量、指向堆中对象的指针,以及定义应用程序控制流的指针。在下面的示例中,变量a和b存储在堆栈中。functionadd(a,b){returna+b}add(4,5)堆(Heap)堆专门用来存放“引用类型”的对象,比如字符串或者对象。以下示例中的Car对象存储在堆中。functionCar(opts){this.name=opts.name}constLightningMcQueen=newCar({name:'LightningMcQueen'})执行后,内存会是这样的:如果你创建更多的Car对象,内存会是这变成:functionCar(opts){this.name=opts.name}constLightningMcQueen=newCar({name:'LightningMcQueen'})constSallyCarrera=newCar({name:'SallyCarrera'})constMater=newCar({name:'Mater'})如果此时进行垃圾回收,则不会发生任何事情,因为根对象(root)对每个对象都有引用。现在让我们将上面的示例稍微复杂一点,并向Car对象添加一些“部件”。functionEngine(power){this.power=power}functionCar(opts){this.name=opts.namethis.engine=newEngine(opts.power)}letLightningMcQueen=newCar({name:'闪电麦昆',power:900})letSallyCarrera=newCar({name:'SallyCarrera',power:500})letMater=newCar({name:'Mater',power:100})如果我们不不再使用Mater,但重新定义它并分配一些其他值,例如Mater=undefined?现在,如果我们不想再使用Mater实例,请为其分配另一个值,例如Mater=undefined。这时候会发生什么?可见Mater已经失去了root对他的引用。然后,当执行下一次垃圾收集时,它的内存将被释放。好了,既然我们都了解了GC的基本原理和执行方式,那我们就来看看V8引擎中的GC是如何实现的吧!垃圾收集方法在我们之前的一篇文章中,我们介绍了Node.js的垃圾收集方法是如何工作的,我强烈推荐阅读这篇文章。这篇文章的要点如下:1.新生代空间&老年代空间堆中有两个“段”,新生代空间(NewSpace)和老年代空间(OldSpace)。新的内存分配都发生在新的空间中,大小只有1-8MB左右,但垃圾回收速度快且频繁。这里存储的对象称为“年轻代”。在老年代空间中,存放的是在新生代空间中还没有被回收,已经提升到这个点的对象。他们被称为“老一代”。这里的内存分配很频繁,但是垃圾回收的开销很大,所以执行的频率不高。2.新生代通常只有20%左右的新生代会被提升到老年代。老年代空间只有快用完了才会被垃圾回收。V8引擎使用两种回收算法:Scavenge和Mark-Sweep。Scavenge回收算法速度快,用于新生代;较慢的Mark-Sweep算法用于老年代。4.Meteor案例研究2013年,Meteor的作者发布了一个他们遇到的内存泄漏示例。有问题的代码如下:vartheThing=nullvarreplaceThing=function(){varoriginalThing=theThingvarunused=function(){if(originalThing)console.log("hi")}theThing={longStr:newArray(1000000).join('*'),someMethod:function(){console.log(someMessage)}};};setInterval(replaceThing,1000)嗯,实现闭包的典型方式是每个函数对象都有指向代表其词法范围的字典式对象的链接。如果在replaceThing中定义的两个函数实际上都使用了originalThing,那么即使originalThing被分配给over和overical,它们都获得相同的对象也很重要,因此这两个函数都破坏了Chrome的V8JavaScript引擎显然足够聪明,可以将变量排除在词法环境之外如果它们没有被任何闭包使用——来自Meteor博客。一般来说,闭包的实现方式是每个函数对象都链接到一个代表其词法作用域的词法对象。如果replaceThing中的两个函数都使用了变量originalThing,那么即使originalThing被多次赋值,也必须保证两个函数总是得到同一个对象,从而保证两个函数共享一个词法作用域。所以问题来了,Chrome的V8JavaScript引擎只会将一个变量从词法环境中隔离出来,如果它没有在任何闭包中使用的话。-流星博客。更多相关阅读FindingamemoryleakinNode.jsJavaScriptGarbageCollectionImprovements-Orinocomemorymanagement.org