当前位置: 首页 > 后端技术 > Node.js

Nodejs内存管理

时间:2023-04-04 00:55:57 Node.js

js运行在不同的宿主机环境中,相应的对内存管理的要求也不同。当宿主环境是浏览器时,由于网页运行时间短,而且只运行在用户机器上(相当于分布式),即使内存占用过大或者有一定的内存泄漏,也不会对最终用户有很大的影响。当宿主环境编程服务器(Node)时,情况就大不相同了。代码本身运行在几台固定机器上(集中式),运行时间长。一旦内存管理不好,就会出现内存膨胀。即使在内存泄漏的情况下,服务器的响应时间也会变长,甚至导致服务崩溃。Nodejs是基于V8构建的,所以Node中使用的JavaScript对象基本上(不是Buffer)都是由V8分配和管理的。V8对内存大小有限制(64位操作系统,单个Node进程可以使用的最大堆内存大小约为1.5GB)。即使服务器内存很大,但是由于V8的限制,Node无法充分利用服务器的资源。即便如此,为什么V8会有这样的限制呢?造成这种限制的原因其实和垃圾回收机制有关。以1.5GB的垃圾回收堆为例,V8做一次小型垃圾回收需要50多ms,做一次全量垃圾回收甚至需要1s以上。在回收过程中,JavaScript线程必须处于暂停执行状态。临时时间过长会对后端服务的性能产生很大的影响。因此,考虑到这一点,V8对堆内存进行了限制。尽管如此,V8还是提供了自定义堆内存大小的方式(--max-old-pace-size),old-space代表老年代,new-space代表新生代。node--max-old-space-size=xxxindex.js//单位是MB//之前还可以通过-max-new-space-size定义新生代堆大小,现在不行了由于内存泄漏当服务器频繁重启时,建议增加堆内存大小,为定位问题争取时间。毕竟对于用户来说,服务响应慢比直接返回错误页面要好。为什么我们需要老一代和新一代?老年代和新生代实际上是分代垃圾回收机制中的不同代,因为没有一种垃圾回收算法是适用于所有场景的,不同的对象生命周期其实需要不同的回收策略才能达到最好的效果,所以V8采用了分代垃圾回收机制,对象根据生存时间分为不同的代,然后对不同代(新生代,老年代)的内存应用更合适更好的算法。新生代的对象存活时间很短,而老年代的对象存活时间很长甚至驻留在内存中。基于此,设计的新一代内存一般都比老一代小很多。V8中新生代最大内存为32M(以64位系统为例),老年代最大内存为1400MB。V8实际使用的堆内存大小是新生代+老年代使用内存的总和(1432MB),但是V8的最大值实际上比使用的内存对的大小大了32M(1464MB)。新生代如何进行垃圾回收?新一代使用称为Scavenge的垃圾收集算法。在Scavenge的具体实现中,主要使用了Cheney算法。Cheney算法将新生代堆一分为二,一个被使用(Fromsemispace),一个空闲(Tosemispace)。创建对象时,它现在分配在From空间中。当需要进行垃圾回收时,检查From空间中的存活对象,然后将存活对象复制到To空间,清除From空间,交换From和To,整个垃圾回收过程就是在两者之间复制存活对象地震空间。对于生命周期较短的场景,存活对象占整个对象的比例比较小,所以Scavenge使用存活对象的复制,但是Scavenge只能使用一半的堆内存,这是典型的以空间换取的例子时间。当一个对象在多次(通常是两次)垃圾回收中幸存下来时,就会被认为是一个长生命周期的对象。一方面新生代堆比较小,另一方面反复复制生命周期长的对象是非常没用的。效率,所以生命周期长的对象会被移到老年代。对象从新生代移动到老年代时有两个对象:1、对象是否是长生命周期的对象(经过垃圾回收)2、To空间使用比例是否超过25%。限制25%的原因是To会在垃圾回收完成后变成From。如果没有限制,From可能很快就用完了,频繁的垃圾回收也会影响效率。老年代是如何进行垃圾回收的?由于老年代在存活对象中占有很大比例,不适合对存活对象进行操作。不适合使用Scavenge算法。因此老年代采用Mark-Sweep和Mark-Compact的结合。Mark-Sweep分为标记和清除两个阶段。在标记阶段,遍历堆中的所有对象,对活对象进行标记。然后,在清除阶段,未标记的对象将被清除。Mark-Sweep解决了内存释放的问题,但是因为没有像Scavenge那样复制对象的操作,所以内存碎片不是连续的。而Mark-Compact就是用来解决内存碎片问题的。Mark-Compact会将幸存的对象移动到一端,移动完成后直接清理边界外的内存,这样会有大块的连续可用内存,但是因为涉及到对象的移动,速度Mark-Compact比Mark-Sweep慢。V8主要使用Mark-Sweep,只有在空间不足以分配新生代中这一生来的对象时才使用Mark-Compact。垃圾收集过程将导致应用程序暂停执行。由于新生代本身空间较小,需要复制的存活对象比例也较小,即使进行全量垃圾回收也影响不大。但是老年代空间大,存活的对象也多,执行全量垃圾回收会导致应用挂起的时间很长,所以V8把老式的标记改成增量更新的方式,让标记和应用程序交替执行,直到标记完成,然后再次收集垃圾。执行后续清理。请注意,清理不是增量的。开发者可以指定强制垃圾回收吗?答案是肯定的,在启动node服务的时候,使用--expose-gcflag$node--expose-gcfile.js这样全局对象就有了执行垃圾回收的功能global.gc();建议更安全写functionforceGC()if(global.gc){global.gc();}else{console.warn('没有GC钩子!以`node--expose-gcfile.js`启动你的程序。');}}参考https://speakerdeck.com/addyo...http://newhtml.net/v8-garbage...https://www.xarg.org/2016/06/...https://v8project.blogspot.co...https://v8project.blogspot.de...https://v8project.blogspot.it...http://alinode.aliyun.com/blo...