垃圾回收机制

1、 引用计数

    引用计数算法是垃圾回收最早的算法,有其优势,也有其劣势,但是现在已经很少有使用了。

原理:为每个对象添加一个计数器,表示对象的引用次数,每当创建一个新的引用指向该对象时其计数器就加1,每当指向该对象的引用失效时计数器就减1。当计数器的值为0时就会被浏览器回收。

优点:

可即刻回收垃圾:当计数器值为0时,会马上回收对象,提高内存使用效率
STW(Stop-The-World)短: 回收垃圾时,浏览器会停止响应其他操作,引用计数不需要遍历堆,有效减少STW时间

缺点:

计数器的增减操作频繁
计数器需要占用一定的内存
实现繁琐,更新引用时很容易导致内存泄露。
循环引用无法回收(最重要的缺点)

2、 标记清除

    现代浏览器使用最多的一种垃圾回收机制。

原理:从名字就可以看出,该算法分为两个步骤:1.标记所有可访问的对象;2.清除所有未被标记的对象。

优点:

算法实现简单。

缺点:

碎片化:清楚后的空间是不连续的,不利于后面空间的分配。
分配速度慢:由于空间碎片化,所以每次分配需要遍历空闲链表。
STW(Stop-The-World)长:两个阶段均要遍历整个堆。

优化:采用标记整理算法或标记压缩算法进行内存整理,避免内存碎片化。

3、 GC优化策略 - 分代回收

    V8 实现了准确式 GC,采用了分代式垃圾回收机制,将内存(堆)按照8:2分为老生代和新生代两部分,其中新生代又按照1:1分为了两块Survivor空间。

    一方面新生代中的对象多是临时对象,另一方面新生代空间中有一半是空闲的,所以只分的两成的空间。

    新生代 GC采用复制算法。在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。如果垃圾回收时,存活的对象太多,To 空间的对象占比大小超过 25 %。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代空间中,就会出现有些对象提前进入老生代的情况。

    当新生代中的对象经历过一次 Scavenge 算法后,会将对象从新生代空间移到老生代空间中。

    老生代 GC采用标记清除算法和标记压缩算法。老年代中主要存放存活时间较长的持久对象,这类对象数量较多。当老年代中的内存消耗超过一定限制时,会先启动标记清除算法,清除对象后会造成堆内存出现碎片的情况,当碎片超过一定限制后会启动压缩算法。在压缩过程中,将活的对象像一端移动,直到所有对象都移动完成然后清理掉不需要的内存。

    在 GC时,浏览器会停止响应其他操作,而一次GC可能需要几百毫秒才能完成。这就会导致一些性能上的问题。为了解决这个问题,2011 年,V8 从 stop-the-world(STW) 标记切换到增量标志。在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。在 2018 年,GC 技术又有了一个重大突破,这项技术名为并发标记。该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行。

相关推荐