吉法师的博客

不知道能否追到喜欢的人呀,今年努力下吧~ 2022.1.4

Node.js的垃圾回收机制

垃圾回收算法

垃圾:无法再被访问的对象或内存空间。

延迟:指平均每次垃圾回收开始到结束需要的时间。

吞吐量:指平均一定时间内能回收多少内存,内存多少这个概念非常广泛,可以指多少个对象,也可以指多少字节的空间,具体的应该看指标应需求而异。

根节点:如全局变量上的对对象的引用、栈上对对象的引用等用户一定能够访问到的地址,是寻找活对象的入口。

1.引用计数

这是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。我们可以为每个对象都增加一个计数器,来记录对这个对象的引用数量,当引用计数归零时,这个对象变成了垃圾。

优点:

  • (1)内存释放及时,当一个对象死亡时其占用的内存马上被释放
  • (2)延迟低,内存释放的时间均匀地分布在各个时间段

缺点:

  • (1)每个对象需要附带一个计数字段的空间
  • (2)引用复制和销毁时需要对改变计数字段,这可能涉及到相对昂贵的原子操作
  • (3)无法处理循环引用,比如两个对象互相引用对方的情况

2.标记清除 Mark-Sweep

除去引用计数,Mark-Sweep是另一个思考方向。它分为标记和清除两个阶段。当垃圾回收被触发时,运行时从有限的根节点(在Javascript里,根是全局对象)出发,对所有能够到达的对象进行标记(一般为深度优先搜索),然后再遍历整个堆,清除所有未被标记的对象。

优点:

  • (1)相比引用计数很难处理循环引用,Mark-Sweep算法总能找到所有无法被引用的对象
  • (2)由于垃圾总被一起批量回收,可能可以提高内存回收的吞吐
  • (3)这个算法实现起来简单

缺点:

  • (1)每个对象需要附带至少一个比特作为标记的空间
  • (2)由于Mark阶段需要在整个堆上随机遍历,对CPU缓存不友好
  • (3)算法的性能与堆的大小相关,当堆非常大,而单次回收对象数量有限时,性能被严重拖累
  • (4)垃圾回收的延迟较高,会使用户代码完全停止一段时间
  • (5)出现内存不连续的状态

3.标记复制 Mark-Copy

Mark-Copy将堆内存一分为二,一个处于使用状态,一个处于闲置状态。当开始垃圾回收时,会检查使用状态的内存块,把存活的对象复制到闲置状态的内存块,完成复制后,两个内存空间交换角色。

相较于Mark-Sweep,其优点有:

  • (1)在回收垃圾的同时也整理内存,避免了内存碎片化的问题
  • (2)非侵入式的算法,不需要对象上的字段(理想是美好的,但现实往往不是)
  • (3)算法的执行时间仅与活对象的数量有关,不需要扫描整个堆
  • (4)分配对象时不需要寻找空闲空间,因为其总在当前使用的堆的末尾

缺点:

  • (1)回收时需要进行大量的内存拷贝
  • (2)内存利用率低,维护了两个堆,却只用了一半的空间

4.标记整理 Mark-Compact

注意Mark-Copy算法需要维护一个额外的堆来作为拷贝活对象的容器。标记整理和标记清除的差别在于对象标记死亡后,在整理内存的过程中,将活着的对象往一端移动,移动完成后,直接清理边界外的内存。

可以说Mark-Compact是Mark-Copy和Mark-Sweep算法的一种整合,其优缺点也只是前两种算法各取部分。

标记清除,标记复制,标记整理特点:

  • (1)标记清除只复制活着的对象,用空间换取时间,速度最快
  • (2)标记复制只清除死亡的对象
  • (3)标记整理是两者的整合,速度最慢

V8的垃圾回收

新生代主要使用Scavenge垃圾回收算法进行管理,主要实现是Cheney算法,将内存平均分为两块,使用空间叫From,闲置空间叫To,新对象都先分配到From空间中。

在空间快要占满时将存活对象复制到To空间中,然后清空From的内存空间,此时,调换From空间和To空间,继续进行内存分配,当满足那两个条件时对象会从新生代晋升到老生代。也就是上面提到的标记复制式的算法

老生代主要采用Mark-Sweep和Mark-Compact算法,一个是标记清除,一个是标记整理。两者不同的地方是,Mark-Sweep在垃圾回收后会产生碎片内存,而Mark-Compact在清除前会进行一步整理,将存活对象向一侧移动,随后清空边界的另一侧内存,这样空闲的内存都是连续的,但是带来的问题就是速度会慢一些。

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


Share