V8
概念
垃圾回收机制
引用计数法
优点:
- 可即刻回收:当被引用数值为 0,对象马上会把自己作为空闲空间连到空闲链表上。也就是说,在变成垃圾的时候就立刻被回收
- 因为是及时回收,那么程序不会暂停去单独使用很长一段时间的 GC,那么最大暂停时间很短
- 不用去遍历堆里面的所有活动对象和非活动对象
缺点:
- 计数器需要占很大的位置
- 最大的劣势是无法解决循环引用无法回收的问题
标记清除法
优点:实现简单,解决了循环引用的问题
缺点:
- 造成内存碎片化
- 再分配时遍历次数多,如果一直没有找到合适的内存块大小,那么会遍历空闲链表(保存堆中所有空闲地址空间的地址形成的链表)一直遍历到尾端。
标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。
V8 对 GC(Garbage Collection)的优化
分代式垃圾回收
V8 采用分代式垃圾回收策略,将堆内存分为新生代和老生代,分别采用不同的垃圾回收器来管控。新生代的对象存活时间短,占内存小,老生代的对象存活时间长,占内存大
新生代垃圾回收
- 垃圾回收管理器将新生代的内存一分为二,分别是
使用区
和空闲区
- 新加入的内存会存放到使用区,当使用区块被写满时执行一次垃圾回收
- 新生代垃圾回收器对使用区的活动对象进行标记,将使用区的活动对象复制进空闲区并进行排序,将非活动对象占用的空间清理掉
- 角色互换,把原来的使用区变成空闲区,把原来的空闲区变成使用区
- 当一个对象经过多次复制后依然存活,它将会被认为是生命周期较长的对象,随后会被移动到老生代中
- 复制一个对象到空闲区时,空闲区空间占用超过了 25%,那么这个对象会被直接晋升到老生代空间中
老生代垃圾回收
- 标记阶段,从一组根元素开始,递归遍历这组根元素,遍历过程中能到达的元素称为活动对象
- 清除阶段,将非活动对象清除掉
- 标记结束后,将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存
并行回收(Parallel)
新生代对象空间就采用并行策略,在执行垃圾回收的过程中,会启动了多个线程来负责新生代中的垃圾清理操作,这些线程同时将对象空间中的数据移动到空闲区域,这个过程中由于数据地址会发生改变,所以还需要同步更新引用这些对象的指针,此即并行回收。
这个过程中,主线程处于全全停顿的状态,不需要考虑内存中对象的引用关系改变
增量标记与懒性清理
增量: 就是将一次 GC 标记的过程,分成了很多小步,每执行完一小步就让应用逻辑执行一会儿,这样交替多次后完成一轮 GC 标记
三色标记法(暂停与恢复):
- 白色指的是未被标记的对象
- 灰色指自身被标记,成员变量(该对象的引用对象)未被标记
- 黑色指自身和成员变量皆被标记
- 最初所有的对象都是白色,意味着回收器没有标记它们
- 从一组根对象开始,先将这组根对象标记为灰色并推入到标记工作表
- 当回收器从标记工作表中弹出对象并访问它的引用对象时,将其自身由灰色转变成黑色,并将自身的下一个引用对象转为灰色
- 一直走下去,直到没有可标记灰色的对象,也就是无可达(无引用到)的对象
- 剩下的所有白色对象都是无法到达的,即等待回收
当恢复 GC 执行时,可以直接通过当前内存中有没有灰色节点来判断整个标记是否完成
写屏障(增量中修改引用)
一旦有黑色对象引用白色对象,该机制会强制将引用的白色对象改为灰色,从而保证下一次增量 GC 标记阶段可以正确标记,这个机制也被称作 强三色不变性
懒性清理
增量标记完成后,惰性清理就开始了。当增量标记完成后,假如当前的可用内存足以让我们快速的执行代码,其实我们是没必要立即清理内存的,可以将清理过程稍微延迟一下,让 JavaScript 脚本代码先执行,也无需一次性清理完所有非活动对象内存,可以按需逐一进行清理直到所有的非活动对象内存都清理完毕,后面再接着执行增量标记
并发回收(Concurrent)
主线程在执行 JavaScript 的过程中,辅助线程能够在后台完成执行垃圾回收的操作,辅助线程在执行垃圾回收的时候,主线程也可以自由执行而不会被挂起。 堆中的对象引用关系随时都有可能发生变化,这时辅助线程之前做的一些标记或者正在进行的标记就会要有所改变,所以它需要额外实现一些读写锁机制来控制这一点