V8 垃圾回收机制
垃圾回收分为两个阶段:
常用 GC 算法 #
标记清除算法 #
标记清除法是现在GC算法的基础。标记清除算法的执行过程分为两个阶段:标记阶段、清除阶段。
- 标记阶段会通过可达性分析将不可达的对象标记出来。
- 清除阶段会将标记阶段标记的垃圾对象清除。 标记清除法的缺陷是回收后会产生大量不连续的内存空间,即内存碎片。
复制算法 #
复制算法会将内存空间分为两块,每次只使用其中一块内存。 复制算法同样使用可达性分析法标记除垃圾对象,当GC执行时,会将非垃圾对象复制到另一块内存空间中,并且保证内存上的连续性,然后直接清空之前使用的内存空间。然后如此往复。 该算法在存活对象少,垃圾对象多的情况下,非常高效。其好处是不会产生内存碎片,但坏处也是显而易见的,就是直接损失了一半的可用内存。
标记压缩算法 #
标记压缩算法可以解决标记清除算法的内存碎片问题。 其算法可以看作三步:
- 标记垃圾对象
- 清除垃圾对象
- 内存碎片整理 标记压缩算法的碎片整理会造成较大的消耗
分代算法 #
首先,标记清除算法、复制算法、标记压缩算法都有各自的缺点,如果单独用其中某一算法来做GC,会有很大的问题。例如,标记清除算法会产生大量的内存碎片,复制算法会损失一半的内存,标记压缩算法的碎片整理会造成较大的消耗。 其次,复制算法和标记压缩算法都有各自适合的使用场景。复制算法适用于每次回收时,存活对象少的场景,这样就会减少复制量。标记压缩算法适用于回收时,存活对象多的场景,这样就会减少内存碎片的产生,碎片整理的代价就会小很多。 分代算法将内存区域分为两部分:新生代和老年代。 根据新生代和老年代中对象的不同特点,使用不同的GC算法。
- 新生代对象的特点是:创建出来没多久就可以被回收(例如虚拟机栈中创建的对象,方法出栈就会销毁)。也就是说,每次回收时,大部分是垃圾对象,所以新生代适用于复制算法。
- 老生代的特点是:经过多次GC,依然存活。也就是说,每次GC时,大部分是存活对象,所以老年代适用于标记压缩算法。
V8 垃圾回收 #
V8 的垃圾回收策略主要基于分代式垃圾回收机制。 V8对内存大小做了限制,64位操作系统最大为1.4GB,32为系统最大为0.7GB。这么做的原因主要有以下两点:
- V8主要是服务于浏览器的,浏览器不太可能遇到需要大内存的场景。
- 64位系统之所以设计成1.4GB的上限是有考量的,因为如果内存超过1.4GB的时候,V8做一次最小的垃圾回收需要50ms以上,做非增量垃圾回收需要1秒以上,在回收过程中会阻塞JS执行,这个时候你会觉得浏览器卡死了。所以设置1.4GB最大内存是一个平衡的选择。 因为垃圾回收会阻塞JS代码,所以怎么高效的进行垃圾回收就显得非常重要了。V8 的垃圾回收策略主要基于分代式垃圾回收机制。
V8如何回收新生代 #
新生代因为存放的是一些存活时间短的对象,所以为了效率考虑,其回收频率也会比较高。其回收策略采用的是标记 + 复制。
如上图所示,新生代内存等分为两个区域,From为活动区域,To为复制区域。 当我们分配对象时,先是在From空间中进行分配,当要触发垃圾回收的时候,From就会先进行标记,标记完成后,将可达对象(活动对象)复制到To区域,然后把From区域整理进行清除,清除完成后From和To区域交换一下。
晋升 #
我们前面说过新生代内存上限是32MB,这点内存很容易就能达到,所以如果一些对象经常存在新生代里的话,很容易新生代内存就占满了,所以V8存在一种晋升机制,但满足下面条件的时候,新生代对象就会移动到老生代区域,简称晋升,这个过程发生在From区域将活动对象拷贝至To区域过程中。
- 一轮垃圾回收(GC)后还存活的新生代对象需要晋升。也就是说上一轮垃圾回收后,这一轮垃圾回收发现你还是活动对象,就需要把你晋升到老生代区域。
- To空间的使用率超过25%,如果超过25%,后续对象直接晋升。这也是个衡量结果,超过25%,后续新进来的对象就会有些不够用了。
V8如何回收老生代 #
老生代存一些持续时间久的对象,比如全局变量,闭包等。它可分配内存大,而且回收频率不像老生代那么频繁。所以它的垃圾回收机制也和不太一样。 老生代主要是用标记清除策略进行垃圾回收,但是前面说过,标记清除会产生碎片化内存,当新生代对象要晋升到老生代,而老生代没有空间可存的时候,老生代就会进行一次标记整理进行空间优化,优化后在进行晋升操作。
增量标记 #
垃圾回收(分标记和回收两个阶段)会阻塞JS执行,全堆垃圾回收带来的停顿时间会较长。 V8 从标记阶段入手,将原本需要一口气停顿完成的动作改成增量标记,即程序执行和垃圾回收穿插着来,这样用户的感知就不明显了。图示如下:
V8 在经过增量标记的改进后,垃圾回收的最大停顿时间可以减少到原本的 1/6 左右。 V8 后续还引入了延迟清理(lazy sweeping)与增量式整理(incremental compaction),让清理和整理动作也变成增量式的。同时还计划引入并行标记和并行清理,进一步利用多核性能降低每次停顿的时间。