浏览器回收机制

97 阅读6分钟

浏览器回收机制

Vic的读书笔记,内容来源于下面这篇文章。

「硬核JS」你真的了解垃圾回收机制吗

juejin.cn/post/698158…

GC是什么

GCGarbage Collection,是工作在引擎内部的用于回收垃圾的机制。

不是所有的语言都有GC的,一般的高级语言里面会自带GC,但C、C++等不存在GC机制,这种就需要程序员手动进行内存的管理。

垃圾回收策略

两种常见的算法策略:

  • 标记清除算法
  • 引用计数算法

标记清除算法

目前在JavaScript引擎中这种算法是最常见的,分为标记清除两个阶段,标记阶段将所有活动对象做上标记,清除阶段则将没有标记的非活动对象销毁。

过程:

  • 垃圾收集器在运行时给内存中的所有变量加上一个标记,假设内存中的所有对象都是垃圾,全部标记为0;
  • 然后从每个根对象开始遍历,把不是垃圾的节点标记改为1;
  • 清理所有标记为0的垃圾,销毁并回收它们占用的内存空间;
  • 最后将所有内存中的对象标记修改为0,等待下一轮垃圾回收。

优点:实现简单

缺点:

  • 内存碎片化
  • 分配速度慢

优化方法:标记整理(Mark-Compact)算法,即在标记结束后,将非垃圾对象向内存一端移动,最后清理掉边界的内存。

img

引用计数算法

目前很少使用,因为问题很多。

策略是记录每个变量值被使用的次数,每次引用赋值,引用次数加一,每次值被覆盖,引用次数减一。当引用次数变为0时,值被回收。

优点:

  • 能够立即回收垃圾
  • 不需要遍历堆里的活动以及非活动对象来清除,只需要在引用的时候计数就可以了

缺点:

  • 需要一个计数器,这个计数器需要占据内存空间
  • 无法解决循环引用无法回收的问题

V8对GC的优化

分代式垃圾回收

将堆内存分为新生代和老生代两个区域,采用不同的垃圾回收策略进行管理。新生代中存放新产生的对象,通常容量较小,老生代存放经过新生代垃圾回收后活下来的对象。

img

新生代的垃圾回收算法:

通过Scavenge算法进行垃圾回收,在此算法的具体实现中,主要采用一种复制式的算法Cheney算法,算法将内存一分为二,一部分处于使用状态被称为使用区,一部分处于闲置状态称为空闲区。新加入的对象被放到使用区,当使用区快被写满的时候,执行一次垃圾清理操作。当进行垃圾回收时,新生代垃圾回收器将使用区的活动对象进行标记,标记完成后将使用区活动对象复制到空闲区并进行排序。随后进入垃圾清理阶段,将非活动对象占用空间清理掉。最后将原来的使用区变为空闲区,原来的空闲区变为使用区。

当一个对象经过多次复制后仍然存活,则认为是声明周期较长的对象,被移动到老生代中。

老生代垃圾回收采用的就是标记清除算法。

优点:通过对不同存活时间的对象进行分区,提高了垃圾回收机制的效率。

并行回收(Parallel)

全停顿(Stop-The-World):由于JS是一门单线程的语言,在进行垃圾回收时会阻塞JS脚本的执行,需要等待垃圾回收完毕再恢复脚本执行。

为了解决这个问题,引入并行回收机制,垃圾回收器在主线程上执行的过程中,开启多个辅助线程,同时执行同样的回收工作。

img

新生代对象空间就采用并行策略。线程在负责垃圾清理的同时还负责将对象空间中的数据移动到空闲区域,由于数据地址发生改变,因此还需要同步更新引用对象的指针。

增量标记与惰性清理

为了减少全停顿的时间,V8对老生代的标记进行优化,将全停顿标记切换到增量标记。

增量就是将一次GC标记的过程分为很多小步,每次执行完一小步就让应用逻辑执行一会儿,这样交替完成一轮GC标记。

img

为了实现采用三色标记法与写屏障

三色标记法主要解决暂停与恢复的问题。使用每个对象的两个标记位和一个标记工作表来实现标记,两个标记位编码三种颜色:白、灰、黑

  • 白色指的是未被标记的对象
  • 灰色指的是自身被标记,成员变量(该对象的引用对象)未被标记
  • 黑色指的是自身和成员都被标记

img

通过有没有灰色节点来判断整个标记过程是否完成。

为了防止GC标记分块暂停后,执行任务程序时内存中标记好的对象引用关系变化产生错误,引入写屏障机制。

img

方法是一旦有黑色对象引用白色对象,该白色对象被强制转换为灰色,以保证下一次增量标记可以正确标记。

惰性清理指的是标记完毕后如果内存足够,不立刻进行清理,让JS脚本代码先执行,并且不需要一次将非活动对象内存清理完,可以按需逐一进行清理直到完毕,后面再接着执行增量标记。

增量标记与惰性清理的优缺点

优点:使得主线程停顿时间大大减少,让用户与浏览器的交互变得更加流畅。

缺点:

  • 并没有减少主线程的总暂停时间,甚至略微增加。
  • 由于写屏障机制的成本,增量标记可能会降低应用的吞吐量。

并发回收(Concurrent)

采用辅助线程在主线程执行JS代码的过程中,在后台完成执行垃圾回收的操作。

img

V8优化总结

V8的垃圾回收策略主要基于分代式垃圾回收机制,新生代垃圾回收器主要使用并行回收,老生代垃圾回收器采用多种方式融合,主要使用并发标记,标记完成之后执行并行清理操作,清理任务采用增量的方式分批在各个JS任务之间执行。

最后

虽然V8有垃圾回收优化,但是仍然需要在代码中避免不利于引擎做垃圾回收的操作,如果有不再用到的内存没有及时回收,这种情况就叫内存泄漏