v8的垃圾回收机制

垃圾回收机制是指将有限的内存中没用了的数据清理掉,腾出空间给新的数据。里头的难点有两:辨识没用了的数据,和清理方法及频率。

在看李兵大大讲解v8引擎是如何工作的时候,发现v8垃圾回收系统的设计优化比想象当中更复杂也更有意思些。以下是我额外翻阅一些资料后综合整理的笔记:

需要回收的垃圾

首先区分v8的两类内存空间:

  1. 栈空间(stack)- 即调用栈。存储执行上下文、原始数据类型(如string、number等)、及对象引用。
  2. 堆空间(heap)- 存储对象的类型、值。

其他还有代码空间之类的划分,感觉我查到的资料细节有些不一致就不多说了。

关于堆啰嗦两句帮助理解:

  • 一种树结构,用于粗略排优先级。
  • 最高或最低优先级的值永远保持在树根位置。添加、删除节点都需要调整树结构。
  • 子节点的最大数量是固定的。
  • 实际在存储中是用连续的分页的空间。也就是为什么通常堆会有字符串表达,如下图:
    img
    伙同上一点,在这个字符串中可以根据层级和子节点数计算出每个节点的位置。

那调用栈是用完就可以轻松扔掉回到上一层的,所以我们谈垃圾回收的时候,谈的是堆空间的清理。

v8垃圾回收的整体流程

v8在设计的时候发现所有内存用同一种方式处理的效益不高,于是在一种叫“代际假说(The Generational Hypothesis)”的指导下进行了优化。假说给内存里的数据对象分了个类:

  • 长存不死的对象 —— 只占少数
  • 和只短时间存在的对象 —— 大多数

据此,堆空间中又分了两块空间:

  • 新生区(New space)——空间较小(约1~8MB可设置修改),清理频率高,使用副垃圾回收器。
  • 老生区(Old space)——空间较大,清理频率低,使用主垃圾回收器。

这两个回收器使用的算法不同,但整体流程都可以概括为:

  1. 检查标记空间中存活和可以回收的对象;
  2. 回收对象占据的内存;
  3. 进行内存整理,减少内存碎片(记得计算机内存是分页的连续空间,对象存储通常也是占用连续的空间,碎片太多对大对象就用不了了)。

新生区用副垃圾回收器(Minor GC)

副垃圾回收器使用的方法叫Scavenger,算法叫Cheney’s Algorithm。

这个方法把新生区对半分为对象区(From space)、空闲区(To space)。新对象会尝试写入对象区,如果发现区域已满,就触发垃圾清理。

具体流程如下:

老生区用主垃圾回收器(Major GC)

主垃圾回收器用的则是标记-清除法(Mark-Sweap):

即:

  1. 标记——从根开始遍历。对还在需要被调用栈引用的对象和它们引用的对象们标记为活动,其余标为垃圾。这里具体的细节还没有挖清楚。
  2. 清除——把标为垃圾的对象占用的内存块标为空。
  3. 整理——剩余存活的对象向一端挪动,整理到一起。

那这些操作其实是很慢的。JS又只用一个主线程,做这样慢的垃圾回收操作会导致其他任何操作无法继续。因此v8又引入了其他一些优化方法,如:

  • 增量回收——把整个操作切割成无数个小任务,与用户JS代码穿插运行。(这招类似React Fibre的任务调度)
  • 并发标记过程——用多个辅助线程一起标记,避免用主线程。
  • 并发清除及整理过程——同标记一样,用多个辅助线程,避免用主线程。
  • 懒触发垃圾清理的过程——延迟清理。

小结

给程序倒个垃圾真不容易啊?!

其实还有许多细节在目前看到的资料里只是粗粗掠过,有待深挖。

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享