V8垃圾回收

JavaScript与Java一样,由垃圾回收机制来进行自动内存管理。但在浏览器中,我们一般不太注重垃圾回收对应用程序构成性能影响的情况。但是拓展到服务端后,对于性能敏感的服务端程序,内存管理的好坏、垃圾回收是否优良,都会对服务器造成影响。

垃圾回收算法

V8的垃圾回收策略主要是基于分代式回收机制。因为在自动垃圾回收的演变过程中,人们发现并没有一种垃圾回收算法可以胜任所有的场景。因为在实际的应用中,对象的生存周期长短不一,不同的算法只能针对特定情况具有最好的结果。

V8的内存分代

在V8中,主要将内存分为新生代老生代两代。

  • 新生代中的对象为存活时间较短的对象。
  • 老生代中的对象为存活时间较长或常驻内存的对象。

V8的整体大小就是新生代内存空间+老生代内存空间。

Scavenge算法

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

Cheney算法是一种采用「复制」的方式实现的垃圾回收算法。它将堆内存一分为二,每一部分空间成为semispace。

在这两个semispace空间中,只有一个处在使用中,另一个处在闲置状态。处在使用状态的semispace空间成为From空间;处在闲置状态的semispace空间成为To空间。

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

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

Scavenge的缺点在于只能使用堆空间的一半,但应为只复制存活的对象,而且对于生命周期较短的场景存活对象只占少部分,所以它在时间效率上有优异的表现。

Scavenge算法是典型的牺牲空间换取时间的算法,因此无法大规模地应用到所有的垃圾回收中。但它非常适合应用到新生代中,因为新生代中对象的生命周期比较短。

当一个对象经过多次复制依然存活时,它将会被认为是生命周期较长的对象。这种对象随后会被移到老生代中,采用新的算法进行管理。这种从新生代移动到老生代的过程成为晋升

对象晋升的条件主要有两个:

  1. 是否经历过Scavenge回收;
  2. To空间的内存占用比超过限制。

在默认情况下,V8的对象分配主要集中在From空间中。对象从From空间中复制到To空间时,会检查它的内存地址来判断这个对象是否经历过一次Scavenge回收。如果已经经历过,那么会将该对象从From空间复制到老生代空间中,如果没有,则复制到To空间中。

另一个判断条件是To空间的内存占用比。当要从From空间复制一个对象到To空间时,如果To空间已经使用了超过25%,则这个对象直接晋升到老生代空间中。

设置25%这个限制值的原因是当这次Scavenge回收完成之后,这个To空间将变成From空间,如果占比过高,那么会影响懂到后续的内存分配。

Mark-Sweep & Mark-Compact

对于老生代中的对象,由于存活对象占较大比重,再采用Scavenge的方式会有两个问题:一个是存活的对象比较多,复制存活对象的效率将会很低;另一个问题依然是浪费一半空间的问题。因此V8在老生代主要采用了Mark-Sweep和Mark-Compact相结合的方式来进行垃圾回收。