垃圾回收

V8的垃圾回收策略主要是基于分代式垃圾回收机制,其根据对象的存活时间将内存的垃圾回收进行不同的分代,然后对不同的分代采用不同的垃圾回收算法。

V8的内存结构

在V8引擎的堆结构组成中,其实除了新生代老生代外,还包含其他几个部分,但是垃圾回收的过程主要出现在新生代和老生代,所以对于其他的部分我们没必要做太多的深入,有兴趣的小伙伴儿可以查阅下相关资料,V8的内存结构主要由以下几个部分组成:

  • 新生代(new_space):大多数的对象开始都会被分配在这里,这个区域相对较小但是垃圾回收特别频繁,该区域被分为两半,一半用来分配内存,另一半用于在垃圾回收时将需要保留的对象复制过来。

  • 老生代(old_space):新生代中的对象在存活一段时间后就会被转移到老生代内存区,相对于新生代该内存区域的垃圾回收频率较低。老生代又分为老生代指针区老生代数据区,前者包含大多数可能存在指向其他对象的指针的对象,后者只保存原始数据对象,这些对象没有指向其他对象的指针。

  • 大对象区(large_object_space):存放体积超越其他区域大小的对象,每个对象都会有自己的内存,垃圾回收不会移动大对象区。

  • 代码区(code_space):代码对象,会被分配在这里,唯一拥有执行权限的内存区域。

  • map区(map_space):存放Cell和Map,每个区域都是存放相同大小的元素,结构简单(这里没有做具体深入的了解,有清楚的小伙伴儿还麻烦解释下)。

上图中的带斜纹的区域代表暂未使用的内存,新生代(new_space)被划分为了两个部分,其中一部分叫做inactive new space,表示暂未激活的内存区域,另一部分为激活状态。

新生代回收

在V8引擎的内存结构中,新生代主要用于存放存活时间较短的对象。新生代内存是由两个semispace(半空间)构成的,内存最大值在64位系统和32位系统上分别为32MB16MB,在新生代的垃圾回收过程中主要采用了Scavenge算法。

引用计数

引用计数主要是IE等旧浏览器在使用,通过计数器分析变量的引用次数,清除没有引用到的变量。对于存在循环引用的情况则无法处理。比如

function cycle() {
    var o1 = {}
    var o2 = {}
    o1.a = o2
    o2.a = o1
}

cycle()

其中 o1 引用了 o2,o2 引用了 o1,在cycle函数执行完 o1,o2 都没有再次引用到,但是引用计数算法判断两者都存在引用。

scavenge 算法

用于V8中的新生代内存,将新生代内存一分为二:From 和 To,在From 和 To 之间转换的过程完成垃圾回收。

老生代回收

在老生代中,因为管理着大量的存活对象,如果依旧使用Scavenge算法的话,很明显会浪费一半的内存,因此已经不再使用Scavenge算法,而是采用新的算法Mark-Sweep(标记清除)Mark-Compact(标记整理)来进行管理。

引用计数的算法,两个变量均存在指向自身的引用,因此依旧无法被回收,导致内存泄漏。

因此为了避免循环引用导致的内存泄漏问题,截至2012年所有的现代浏览器均放弃了这种算法,转而采用新的Mark-Sweep(标记清除)Mark-Compact(标记整理)算法

标记清除

早期V8中堆内存采用的一种清除算法,全局扫描堆内存找出未使用到的对象进行标记并清除,由于未进行内存整理会存在内存碎片。

避免内存泄露

  1. 尽可能少的创建全局变量

  2. 手动清除定时器

  3. 少用闭包

  4. 清除DOM引用

  5. 使用弱引用

最后更新于