V8 引擎的垃圾回收机制

V8 引擎将内存分为新生代和老生代

由于不同对象的生存周期不同,只用一种回收策略来解决问题,这样效率会很低。所以V8采用了一种代回收的策略,将内存分为两个生代:新生代(new generation)和老生代(old generation)。

新生代:新创建的或只经历过一次垃圾回收的对象。

  • 特点:大多数的对象被分配在这里,这个区域很小但是垃圾回特别频繁。

老生代:经历过多次垃圾回收的对象。

  • 特点:所保存的对象大多数是生存周期很长的甚至是常驻内存的对象,而且老生代占用的内存较多。

如何判断是否可以被回收:

  • 标记清除

当函数中声明一个变量时,就将这个变量标记为“进入环境”。而当变量离开环境时,则将其标记为“离开环境”。

首先,垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(可以使用任何标记方式)。

然后,它会去掉运行环境中的变量以及被环境中变量所引用的变量的标记。

此后,依然有标记的变量就被视为准备删除的变量,原因是在运行环境中已经无法访问到这些变量了。

最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

  • 引用计数

引用计数的垃圾收集策略不太常见。含义是跟踪记录每个值被引用的次数。

当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。

如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,改变了引用对象,则该值引用次数减1。

当这个值的引用次数变成0时,则说明没有办法再访问这个值了,在垃圾收集器下次再运行时,它就会释放内存。

 垃圾回收算法:

新生代为存活时间较短的对象,老生代为存活时间较长或常驻内存的对象,分别对新老生代采用不同的垃圾回收算法来提高效率。

  • 新生代垃圾回收算法

新生代使用Scavenge算法进行回收。在Scavenge算法的实现中,主要采用了Cheney算法。

Cheney算法算法是一种采用复制的方式实现的垃圾回收算法。新生代的空间被分为To和From空间。To一般是闲置的,对象都被存放在From空间,当From空间满了就会执行Scavenge 算法进行垃圾回收。当开始进行垃圾回收算法时,会检查From空间中的存活对象,这些存活对象将会被复制到To空间中(复制完成后会进行紧缩),而非活跃对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。也就是说,在垃圾回收的过程中,就是通过将存活对象在两个semispace之间进行复制。可以很容易看出来,使用Cheney算法时,总有一半的内存是空的。但是由于新生代很小,所以浪费的内存空间并不大。而且由于新生代中的对象绝大部分都是非活跃对象,需要复制的活跃对象比例很小,所以其时间效率十分理想。

  • 老生代垃圾回收算法

老生代占用内存较多(64位为1.4GB,32位为700MB),如果使用Scavenge算法,浪费一半空间不说,复制如此大块的内存消耗时间将会相当长。所以Scavenge算法显然不适合。V8在老生代中的垃圾回收策略采用Mark-Sweep和Mark-Compact相结合。

  Mark-Sweep(标记清除)
标记清除分为标记和清除两个阶段。在标记阶段需要遍历堆中的所有对象,并标记那些活着的对象,然后进入清除阶段。在清除阶段总,只清除没有被标记的对象。由于标记清除只清除死亡对象,而死亡对象在老生代中占用的比例很小,所以效率较高。

标记清除有一个问题就是进行一次标记清除后,内存空间往往是不连续的,会出现很多的内存碎片。如果后续需要分配一个需要内存空间较多的对象时,若所有的内存碎片都不够用,将会使得V8无法完成这次分配,提前触发垃圾回收。

  Mark-Compact(标记整理)
标记整理正是为了解决标记清除所带来的内存碎片的问题。标记整理在标记清除的基础进行修改,将其的清除阶段变为紧缩极端。在整理的过程中,将活着的对象向内存区的一段移动,移动完成后直接清理掉边界外的内存。紧缩过程涉及对象的移动,所以效率并不是太好,但是能保证不会生成内存碎片。

在V8的回收策略中,Mark-Sweep和Mark-Conpact两者是结合使用的。主要使用标记清除,在空间不足以对从新生代中晋升过来的对象进行分配时,才使用标记整理。

对象的晋升:

当一个对象满足某些条件,就会被移动到老生代,这称为对象的晋升。具体标准有两种:

判断是对象否已经经过一次 Scavenge 回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。

其次判断To 空间的内存使用占比是否超过限制。当对象从 From 空间复制到 To 空间时,若 To 空间使用超过 25%,对象直接晋升到老生代中。设置 25% 的原因主要是因为两个空间会交换位置,如果 To 空间的内存太小,会影响后续的内存分配。

 总结:

V8的垃圾回收机制分为新生代和老生代。

新生代主要使用Scavenge进行管理,将内存平均分为两块,使用的叫From空间,闲置的叫To空间。新对象都先分配到From空间中,回收时先判断是否存活,如果存活将存活对象复制到To空间中,清空From的内存空间后,再调换From空间和To空间,继续进行内存分配。且满足晋升条件时对象会从新生代晋升到老生代。

老生代主要采用Mark-Sweep和Mark-Compact算法,一个是标记清除,一个是标记整理。两者不同的地方是,标记清除在垃圾回收后会产生碎片内存,而标记整理在清除前会进行一步整理,将存活对象向一侧移动,随后清空边界的另一侧内存,这样空闲的内存都是连续的,但速度会慢一些。在V8中,老生代是标记清除和标记管理共同进行管理的。

由于在进行垃圾回收的时候会暂停应用的逻辑,对于新生代方法由于内存小,每次停顿的时间不会太长,但对于老生代来说每次垃圾回收的时间长,停顿会造成很大的影响。为了解决这个问题 V8 引入了增量标记的方法,将一次停顿进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行。

posted @ 2021-09-28 19:40  辉太狼`  阅读(357)  评论(0编辑  收藏  举报