V8中的垃圾回收算法

前言

本文内容来自于对《垃圾回收的算法与实现》内容的总结算法

V8是啥编程

这个你们都知道,V8 全称是V8 JavaScript Engine,一个用C++写的JavaScript引擎。markdown

垃圾回收又是啥编程语言

垃圾回收的英文是 Garbage Collection,简称GC。在代码运行的过程当中,全部的数据都会存放在内存空间,若是没有GC,开发者就必须手动进行内存管理,否则总有一天内存会被占满,进而致使进程崩溃,甚至系统崩溃。GC的做用就是让计算机自动帮开发者进行内存管理,把内存中不须要再次使用的垃圾回收掉。函数

本文讲的只是V8中的垃圾回收算法,而目前已有的垃圾回收算法远不止这几种,并且目前没有完美的垃圾回收算法。spa

术语解释

指针

GC时,相应的对象会被销毁或者保留。这时候咱们能够经过对象的指针去搜寻其余对象。设计

指针通常指向对象的首地址。其实能够简单理解为JS中的引用地址。3d

指动态存放对象的内存空间。JS中,对象存放在堆中。指针

mutator

mutator的实体是“应用程序”code

我的理解,在JS的场景下,它指的就是V8引擎。在V8运行的过程当中,会不断的去生成对象,修改对象之间的引用关系(即更新了指针)。这些改变就会带来垃圾,这时候就须要GC

活动对象/非活动对象

活动对象指堆中的能被mutator引用的对象。

非活动对象指堆中的不能被mutator引用的对象,即内存垃圾。

在下文中,屡次说起的一个名词是根。什么是根呢?术语上就是能够经过mutator直接引用的对象。举个例子

const obj = new Object(); // A对象
obj.child = new Object(); // B对象
复制代码

第一行代码中,咱们建立了一个对象A,它的引用地址赋值给了obj。第二行代码中,咱们又建立了一个对象,它的引用地址赋值给了obj.child。那么此时堆以下图所示 这里因为咱们能够经过全局变量obj找到A,因此A是活动对象,而后咱们又能够经过A找到B,因此B也是活动对象。那么这个全局变量obj就是一个根。

分代式垃圾回收

将内存空间中的对象分为两类,新生代和老年代。新建立的对象存放在新生代中,通过新生代中的GC后,某些对象依然存活。将存活了数次的对象看成老年代对象来处理。

新生代对象->老年代对象的过程,咱们称之为晋升。

GC的分类

GC有两种类型,保守式和准确式。

保守式GC(Conservative GC)

GC时不能识别一个东西是否是指针时,这个时候的根被称为不明确的根。

举个例子,咱们定义了全局变量a和局部变量obja是一个数值,obj是一个指针(引用地址),引用地址跟值a同样,这个时候GC很难分辨出a究竟是指针仍是值。因而,保守处理,把它当成一个指针,当obj指向的对象应该被垃圾回收时,因为全局变量a的存在,它不会被垃圾回收。这就是保守式GC

window.a = 0x00d0caf0; // 伪代码,固然是会报错的
const obj = new Object(); // 建立了对象,地址为0x00d0caf0
复制代码

在保守式GC的场景下,对象不可以被移动。由于若是移动了对象,意味着对象的引用地址会发生变化,那么上面的obj相应的会重写成移动后的引用地址,与此同时,全局变量a也会被重写,这就很是恐怖了。因此对象不可以被移动。

准确式GC(Exact GC)

顾名思义,准确式GC可以正确识别出哪些内容是值,哪些内容是指针。要实现准确式GC,依赖于编程语言的处理,意味着成本的增长,这里再也不赘述。

V8中的GC

V8中实现了准确式GC

GC算法方面采用了分代垃圾回收,结构以下。

GC复制算法(By Cheney)

GC复制算法将内存空间分为FromTo,当From空间占满时,将From空间中的活动对象(划重点)复制到To空间中,非活动对象回收掉,而后FromTo互换。显而易见,FromTo空间的大小要彻底一致。

GC复制算法有不少种,好比 Robert R.Fenichel 和 Jerome C.Yochelson研究出来的和 C. J.Cheney 研究出来的。下面介绍的是Cheney研究出来的算法。

算法流程

在Cheney的复制算法中,算法流程以下

初始状态

首先,复制全部从根直接引用的对象,BG。注意,新的B引用了From中的A,新的G仍是在引用From中的B(为区分,写做B1)和E

而后,搜索B1,发现引用了A,因而把A复制到To中,同时修正B中的指向。

接着,搜索G,把E复制到To中,而且G指向B1的指针换到了B

最后,搜索AE,发现没有引用的对象,清空From,将FromTo空间互换,复制算法结束。

优势

  • 吞吐量大
  • 不会发生碎片化
  • 没有递归调用函数。cheney的算法中使用迭代的方式进行复制,这意味着没有过多的消耗栈

缺点

  • 内存空间利用率小,很明显,咱们每次只能使用一半的内存空间来分配对象
  • 不兼容保守式GC,由于移动了对象

触发时机

From空间没有分块的时候

GC 标记-清除算法

本算法分为两个阶段

  • 标记阶段: 这个阶段会递归遍历堆中全部的活动对象,打上标记
  • 清除阶段: 遍历整个堆中全部对象,把全部没有被标记的对象回收掉,堆越大,回收耗时越长

很明显,通过这两个阶段后,不能利用的内存空得以再次被利用。

优势:

  • 算法实现简单
  • 能够适用于保守式GC的场景

缺点:

屡次GC后会致使内存中出现碎片。碎片化的后果是,即便可用内存的总空间够用,也会由于单个空间不够用致使不可以分配内容(这个时候就要用到下面提到的标记-压缩算法了)

假设内存空间一共5KB,下图中A、B、C、D、E各占了1KB,经历一次GC后,B和D被回收,内存空间中剩余2KB,此时分配一个大小为2KB的对象到内存空间中,没法分配,由于剩余的2KB空间并不连续。

触发时机

  • 老年代空间中某一个空间没有分块的时候
  • 老年代空间中分配了必定数量对象的时候(启动新生代GC时会检查)
  • 老年代空间中没有新生代空间大小的分块的时候(这个时候没法保证新生代GC时的晋升)

GC 标记-压缩算法

  • 标记阶段:V8采用深度优先的方式进行标记,即标记了对象,随后会去标记这个对象的子对象。深度优先遍历时,通常采用递归操做,递归时,天然须要用到栈,在V8中,这个栈由V8自行生成。栈所用的空间是新生代的From空间。由于老年代GC以前,必然会执行新生代GC,这个时候From空间是空的,既然空都空了,不如就把栈放在这里,不用白不用xd。

  • 压缩阶段:

压缩前,能够看到老年代空间中存在不少空白小方块,即内存碎片

压缩时,将内存对象按顺序逐个移动到内存空间的前面

压缩后,能够看到空白小方块已是连续的了,不存在内存碎片

优势

  • 有效利用堆,不会像复制算法那样只能利用半个堆,也不会存在内存碎片

缺点

  • 在算法过程当中,要不断的去移动对象,成本相对于其它算法很是之高

触发时机

  • 老年代空间中的碎片到达必定数量的时候

后记

一开始的时候,标题是《垃圾回收算法》,然而。。。了解深了以后发现,除了上面这些,还有引用计数法,增量式垃圾回收,RC Immix算法等等。即便是相同算法,不一样设计者也有必定的区别。不一样语言的GC算法实现上也有必定的区别。内容多的离谱,因而在标题前面加上了“V8中的”。

这个文涉及的东西实际开发中并不常见,并且术语比较多,不免有地方写错了,有朋友发现了的话,麻烦评论区留个言。

相关文章
相关标签/搜索