V8的垃圾回收

205 阅读2分钟

V8 垃圾回收

调用栈回收

栈中有一个记录当前执行状态的指针,每当指针下移就意味着之前的上下文为无效上下文,可以被覆盖,相当于销毁。

堆内存回收

代际假说 分代收集

  1. 大部分对象的存活时间都很短——新生代
  2. 不被回收的对象,会存活更久——老生代

新生代垃圾回收 Scavenge

  • 将新生代空间对半分,一半是对象区域,一般是空闲区域。
  • 新加入的对象都会被放入对象区域,每当对象区域快满,回收器会标记所有的垃圾并清理,之后把存活的对象赋值进空闲区并排列(清理内存碎片)。最后翻转对象区域与空闲区域。

Scavenge 算法由于需要复制对象,比较耗时,所以新生代空间比较小。但这会使新生代容易装满,会频繁触发垃圾回收。为解决此问题,经过两次垃圾回收之后还存活的对象将会放入老生代。

老生代垃圾回收 Mark-Sweep & Mark-Compact

  • Mark-Sweep:从根节点出发跟踪其中的指针,遍历所有的元素并标记为活动对象,没被标记到的则判断为垃圾数据并清除。
  • Mark-Compact:对一块内存多次标记清除之后会触发。同样从根节点遍历,标记活动对象,并把这些对象全部向一端移动,清理掉端边界之外的内存。

标记 marking

  • 根节点:当前的活动对象——全局对象或者当前活动的函数

  • 三色标记:最初所有的对象都是白色,意味着收集器还没发现它们,被发现之后变为灰色。当收集器访问其所有字段(fields)时,灰色对象变为黑色。当不再有灰色对象时,标记结束。所有剩余的白色对象都代表无法访问,可以安全回收。

Reducing marking pause

垃圾回收运行于主线程,会阻塞 JavaScript 执行,老版本的 V8 全停顿(Stop-The-World)。但如果时间过长会影响用户体验,当前 V8 使用的是增量标记。

增量标记 Incremental Marking

增量标记期间,收集器将标记工作拆分为更小的块,并允许应用程序在这些块之间运行。但是增量标记并不 free,应用必须通知收集器有关更改对象图谱的所有操作。

write_barrier

JavaScript 每次对一个对象进行 write 操作后,V8 都会插入 write_barrier 代码。该代码保证应用程序无法向收集器隐藏活动变量,即不可能出现黑色对象指向白色对象的情况。

// Called after `object.field = value`.
write_barrier(object, field_offset, value) {
  if (color(object) == black && color(value) == white) {
    set_color(value, grey);
    marking_worklist.push(value);
  }
}