浏览器垃圾回收机制

GC是什么

GC: Garbage Collection

垃圾产生 && 回收

1
2
let a = { name: '炼狱先生' }
a = [1, 2, 3]

{ name: '炼狱先生' } 保存在堆内存中,当a被重新赋值数组[1,2,3],那么它的引用关系就没有了,即变成了垃圾。

栈内存所存的基础数据类型大小是固定的,有操作系统自动分配和释放的;堆内存所存数据大小不固定,系统无法自动释放,需要js引擎手动释放

常见的垃圾回收策略

常见的两种:标记清除和引用计数

标记清除

执行gc时,递归遍历所有的对象,对可达对象进行标记,不可达对象当成垃圾回收。

类似:

  1. 垃圾收集器在运行时给内存的所有变量加上一个标记,假设所有对象都是垃圾,全标记0;
  2. 然后从跟对象开始遍历,把不是垃圾的节点改成1;
  3. 结束后清理所有标记为0的垃圾,销毁回收他们锁占用的空间;
  4. 最后,把所有对象修改为0,等待下一轮垃圾回收

引用计数

就是判断一个对象的引用数,引用数为0就回收,大于0就不回收。

步骤:

  1. 当声明了一个变量并将一个引用对象赋值给该变量时,这个值的引用次数为1;
  2. 这个值又赋给另一个变量,那么引用次数加1;
  3. 该变量的值被其他值覆盖了,一个引用对象引用次数减1;
  4. 当这个值引用次数为0,说明没有被引用了,就被回收。
  • 缺点:出现循环引用,导致引用一直存在。

V8的垃圾回收算法

分代回收

V8将堆分为两个空间,一个叫新生代,一个叫老生代,新生代存放小的、存活周期短的对象。它只有1-8M的容量;老生代存放大的、存活周期长的对象。

V8使用不同的垃圾回收器和不同的回收算法:

  • 副垃圾回收器 + Scavenge算法:负责新生代
  • 主垃圾回收器 + Mark-Sweep(标记清除) + Mark-Compact(标记整理):老生代

新生代

Scavange算法

Scavange算法将新生代堆分为两部分,分别叫from-space和to-space,新分配的对象被放入from-space,from-space占满时,gc就启动了,标记活动对象和非活动对象,复制from-space的活动对象到to-space并排序,清除from-space中的非活动对象,最后将from-space和to-space进行角色互换,以便下一次的回收。

新生代对象晋升到老生代对象的两种情况:

  • 一个对象经过多次复制依然存活;
  • 复制一个对象到to-space时,占用超过25%

老生代

老生代为什么不用Scavenge算法:

  • 反复复制存活率高的对象,效率低;
  • 老生代内存空间大,使用以空间换时间,空间非常浪费
Mark-Sweep(标记清理)
  • 标记阶段:对老生代对象进行第一次扫描,对活动对象进行标记;
  • 清理阶段:对老生代对象进行第二次扫描,清除未标记的对象,即非活动对象。

清除非活动对象后,留下了零零散散的空位。

Marck-Compact(标记整理)

即在标记清理结束后,把剩下的活动对象整理到内存的一侧,整理完成后,直接回收边界的内存。

全停顿问题

js是一个单线程,代码运行需要用到js引擎,垃圾回收也需要用到js引擎,冲突了怎么办?垃圾回收优先于代码执行,会先停止代码的运行,等到垃圾回收完毕,再执行js代码,这个过程被称为全停顿。

新生代空间小,配合算法停顿时间短;但老生代某些时候对象较多,停顿时间长,使得页面出现了卡顿现象。

增量标记

就是将一次GC标记的过程,分成了很多小步,执行一小步再执行应用逻辑,交替多次完成一轮GC标记

增量标记后,每次暂停后如何恢复标记?如果执行程序时标记的对象被修改了怎么处理?对应引入了三色标记法与写屏障法

三色标记法

三色标记法即使用两个标记位和一个标记工作表来实现标记。

最初所有对象都被标记白色,从一组根对象开始,将这组对象标记为灰色并推入标记工作表中,当回收器从工作表中弹出并访问他的引用对象时,自身由灰色转变为黑色,并将下一个引用对象转为灰色,直到没有可标记为灰色的对象了,那么剩下的所有白色都是无法到达的,即需要回收的。

写屏障法

写屏障:增量中修改引用。
如果在增量标记时,第一次对象被引用,而后代码执行时修改了他的引用,那么第二次增量时会将被引用的新对象强制由白色对象改为灰色,从而保证下一次会被正确标记,又叫强三色不变性。

惰性清理

增量标记完成后,接着就是清除阶段了。但如果当前可用内存足够让代码执行,gc就选择了延迟清理,或者只清理部分,让js代码先执行,这个优化叫做惰性清理。

并发回收

在执行javascript的过程中,由辅助线程在后台完成执行垃圾回收的操作。主线程和辅助线程同时进行。

并行回收

并行GC:主线程和辅助线程同时执行同样的GC工作,由辅助线程来分担主线程的GC工作。

V8当前的垃圾回收机制

  • 2011年,V8应用增量标记机制;
  • 2018年,Chrome64和Nodejs启动并发标记,同时添加并行技术。

这几种技术在垃圾回收时融合使用。

参考

  1. 浏览器的垃圾回收机制
  2. 你真的了解垃圾回收机制吗
  3. 赠你13张图,助你20分钟打败了「V8垃圾回收机制」