当前位置:网站首页>Node-2.垃圾回收机制

Node-2.垃圾回收机制

2022-08-11 05:24:00 想要成为程序媛的DUDUfine

NodeJS的垃圾回收机制

对于每一门编程语言来说,如何进行垃圾回收都是很重要的。

JavaScript和Java一样,由垃圾回收机制来进行自动内存管理,不需要像C/C++程序员那样需要在编写代码过程中关注内存的分配和释放问题。

对于浏览器开发时很少遇到因为垃圾回收对应用程序造成性能影响,当在服务器端,内存管理的好坏、垃圾回收状况是否优良,对于性能敏感的服务器都会构成影响。

Node是基于V8构建,所以在Node中使用JavaScript对象基本都是通过V8自己的方式来进行分配和管理。在浏览器端是绰绰有余,但是在Node作为服务器时却限制了大内存的使用,从而影响了性能。

V8提供了调整内存限制的方法可以在Node无法分配足够内存给JavaScript时放宽V8默认的内存限制,从而避免执行过程中稍微多用一些内存就导致崩溃。

V8的垃圾回收机制

V8的垃圾回收策略主要基于分代式垃圾回收机制

在V8中,主要将内存分为新生代老生代两代。
新生代中的对象为存活时间较短的对象
老生代中的对象为存活时间较长或常驻内存的对象

V8堆的整体大小就是新生代所用内存空间加上老生代的内存空间。新生代和老生代内存空间的最大值限制可以在启动时就指定。
V8内存无法根据使用情况自动扩充,当内存分配超过极限值,会引起进程出错。

V8默认设置下,64位系统老生代为1400MB,32位系统下为700MB;
新生代内存最大值在64位系统和32位系统上分别为32MB和16MB。
默认V8堆内存的最大值在64位系统上为1464 MB,也就是约1.4GB;32位系统上则为732MB,也就是约0.7GB。

V8的堆内存示意图

新生代 —— 使用Scavenge算法进行垃圾回收

在分代的基础上,新生代中的对象主要通过Scavenge算法进行垃圾回收。

Scavenge算法

Scavenge具体实现主要才用了Cheney算法
Cheney算法是一种采用复制的方式实现的垃圾回收算法。将堆内存一分为二,每一部分空间称为semispace。在这两个semispace空间中,处于使用状态的semispace空间称为From空间,处于闲置状态的空间称为To空间

当分配对象时,先是在From空间中进行分配.当开始进行垃圾回收时,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。

简而言之,在新生代垃圾回收的过程中,就是通过将存活对象在两个semispace空间之间进行复制

Scavenge是典型的牺牲空间换取时间的算法,所以无法大规模应用到所有垃圾回收中,但是非常适合应用在新生代中,因为只复制存活对象,而新生代中对象的生命周期比较短(存活下来的对象少),在时间上有优异的表现。

实际使用的堆内存是新生代中的两个semispace空间大小和老生代所用内存大小之和。

当一个对象经过多次复制依然存活时(From空间中的存活对象在复制到To空间之前需要进行检查),它将会被认为是生命周期较长的对象。这种较长生命周期的对象随后会被移动到老生代中,采用新的算法进行管理。对象从新生代中移动到老生代中的过程称为晋升

晋升到老生代的条件为:

  • 是否经历过Scavenge回收
  • 从From空间复制对象到To空间时,如果To空间已经使用超过25%
    设置25%的限制值是为了当前完成回收后,当前空间变成From空间,如果占比过高,会影响后续内存分配。

老生代 —— Mark-Sweep 和 Mark-Compact

Mark-Sweep

Mark-Sweep是标记清除的意思,它分为标记清除两个阶段。

Mark-Sweep在标记阶段遍历堆中的所有对象,并标记活着的对象,在随后的清除阶段中,只清除没有被标记的对象

Mark-Sweep最大的问题是在进行一次标记清除回收后,内存空间会出现不连续的状态。这种内存碎片会对后续的内存分配造成问题。

Mark-Compact

Mark-Compact标记整理的意思,和Mark-Sweep的差别在于对象在标记为死亡后,在整理的过程中,将活着的对象往一端移动,移动完成后,直接清理掉边界外的内存,解决Mark-Sweep的内存碎片问题。

由于Mark-Compact需要移动对象,所以它的执行速度不可能很快,所以在取舍上,V8主要使用Mark-Sweep,在空间不足以对从新生代中晋升过来的对象进行分配时才使用Mark-Compact。

3种垃圾回收算法的简单对比:

回收算法Mark-Sweep(标记清除算法)Mark-Compact(标记整理算法)Scavenge
速度中等最慢最快
空间开销少(有碎片)少(无碎片)双倍空间(无碎片)
是否移动对象

Incremental Marking

应用背景:
为了避免JavaScrip应用逻辑和垃圾回收器同时执行导致的混乱,垃圾回收执行时需要将应用逻辑暂停下来,等执行完垃圾回收后再恢复执行应用逻辑,这种行为被称为 “全停顿”(stop-the-world)

在V8的分代式垃圾回收中,一次小垃圾回收只收集新生代,由于新生代默认配置得较小,且其中存活对象通常较少,所以即便它是全停顿的影响也不大。但V8的老生代通常配置得较大,且存活对象较多,全堆垃圾回收(full 垃圾回收)的标记、清理、整理等动作造成的停顿就会比较可怕。

为了降低全堆垃圾回收带来的停顿时间,V8先从标记阶段入手,将原本要一口气停顿完成的动作改为增量标记(incremental marking),也就是拆分为许多小“步进”,每做完一"步进"就让JavaScript应用逻辑执行一小会儿,垃圾回收与应用逻辑交替执行直到标记阶段完成。

V8在经过增量标记的改进后,垃圾回收的最大停顿时间可以减少到原本的1/6左右。

V8后续还引入了延迟清理(lazy sweeping)增量式整理(incremental compaction),让清理与整理动作也变成增量式的。同时还计划引入并行标记与并行清理,进一步利用多核性能降低每次停顿的时间。

垃圾回收是影响性能的因素之一,想要高性能的执行效率,需要注意让垃圾回收尽量少地进行,尤其是全堆垃圾回收。

原网站

版权声明
本文为[想要成为程序媛的DUDUfine]所创,转载请带上原文链接,感谢
https://blog.csdn.net/DUDUfine/article/details/104756312