V8-GC

V8 的垃圾回收机制

它与Java一样, 由垃圾回收机制来进行自动内存管理

V8的主要垃圾回收算法

  • V8的内存分代

    V8的垃圾回收策略主要基于分布式垃圾回收机制。在V8中,主要讲内存分为新生代和老生代两代。新生代中的的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。

  • Scavenge算法

    在分代的基础上,新生代的对象主要通过Scavenge算法进行垃圾回收。在Scavenge的具体实现中,主要采用看Cheney算法

    Cheney算法是一种采用复制的方式实现的垃圾回收算法。它将内存一分为二,每一部分空间称为semispace。在这两个semispace空间中,只有一个处于使用中(From空间),另一个处于闲置状态(To空间)。当我们分配对象时,先在From空间中进行分配。当开始进行垃圾回收时,会检查From空间的存活对象,这些存活对象会被复制到To空间中,而非存活对象占用的空间将会释放。完成复制后,From空间和To空间的角色发生对换。

    算法优缺点

    缺点是只能使用堆内存中的一半,这是由划分空间和复制机制所决定。但由于其只复制存活的对象,并且对于生命周期短的场景存活对象只占少部分,所以它在时间效率上有优异的表现。适合应用于新生代中

  • 标记清除 Mark-Sweep 和标记整理 Mark-Compact

    对于老生代的对象,由于存活对象占较大比重,再采用Scavenge算法的方式会有两个问题:一个是存活对象较多,复制存活对象的效率将会降低;另一个是浪费一半的内存空间。故而,在V8的老生代中主要采用标记清除和标记整理相结合的方式进行垃圾回收。

    标记清除分为标记和清除两个阶段:首先标记出所有的需要回收的对象,在标记完成以后统一回收所有被标记的对象。其最大的问题就是在进行一次标记清除回收后,内存空间会出现不连续的状态。这种内存碎片会对后续的内存分配造成问题,因为很可能出现需要分配一个大对象的情况。这时所有的碎片空间都无法完成此次分配,就会提前出发垃圾回收,而这次回收是不必要的。

    为了解决内存碎片化问题,标记整理就出现了。它分为标记和整理两个阶段:首先标记出所有需要回收的对象,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。由于其需要移动对象,故它的执行效率并不可能很快。所以在取舍上,V8主要使用标记清除,在空间不足以对从新生代中晋升的对象进行分配时才使用标记整理

  • 增量标记 Incremmental Marking

    为了避免出现JS应用逻辑和垃圾回收器看到不一致的情况,垃圾回收的3种基本算法都需要将应用逻辑暂停下来,待执行完来及回收后再恢复执行应用逻辑。这种行为成为“全停顿”。由于老生代空间通常配置大,且存活对象较多,全堆垃圾回收的标记、清除、整理等动作造成的停顿比较可怕,故引入了增量标记,也就是拆分为许多小“步进”,每做完一“步进”就让JS应用逻辑执行一小会儿,垃圾回收与应用逻辑交替执行直到标记阶段完成。

    V8在经过增量标记的改进后,垃圾回收的最大停顿时间可以减少到原来的1/6左右。V8后续还引入了延迟清理、增量式整理,让清除和整理也变成增量式的。