图解 Google V8 # 20 :垃圾回收:V8的两个垃圾回收器是如何工作的?

Posted 凯小默

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图解 Google V8 # 20 :垃圾回收:V8的两个垃圾回收器是如何工作的?相关的知识,希望对你有一定的参考价值。

说明

图解 Google V8 学习笔记

垃圾数据是怎么产生的?

例子:

window.test = new Object()
window.test.a = new Uint16Array(100)

上面代码内存布局图:

在上面的基础上,添加代码执行:

window.test.a = new Object()

此时的内存布局:a 属性之前是指向堆中数组对象的,现在已经指向了另外一个空对象,此时堆中的数组对象就成为了垃圾数据。

V8 虚拟机是怎么实现垃圾回收的?

垃圾回收算法

目前 V8 采用的可访问性(reachability)算法来判断堆中的对象是否是活动对象。具体地讲,这个算法是将一些 GC Root 作为初始存活的对象的集合,从 GC Roots 对象出发,遍历 GC Root 中的所有对象:

  • 通过 GC Root 遍历到的对象,该对象是可访问的(reachable),那么必须保证这些对象应该在内存中保留,称可访问的对象为活动对象
  • 通过 GC Roots 没有遍历到的对象,则是不可访问的(unreachable),那么这些不可访问的对象就可能被回收,称不可访问的对象为非活动对象

什么是 GC Roots?

在浏览器环境中,GC Root 有很多,通常包括了以下几种 (但是不止于这几种):

  • 全局的 window 对象(位于每个 iframe 中);
  • 文档 DOM 树,由可以通过遍历文档到达的所有原生 DOM 节点组成;
  • 存放栈上变量。

垃圾回收是怎么实现?

一般来说,频繁回收对象后,内存中就会存在大量不连续空间,这些不连续的内存空间称为内存碎片

垃圾回收的大致流程:

  1. 通过 GC Root 标记空间中活动对象和非活动对象。
  2. 回收非活动对象所占据的内存:就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象。
  3. 做内存整理:当内存中出现了大量的内存碎片之后,如果需要分配较大的连续内存时,就有可能出现内存不足的情况,所以最后一步需要整理这些内存碎片。

代际假说 The Generational Hypothesis

代际假说是垃圾回收领域中一个重要的术语,特点:

  1. 大部分对象在内存中存活的时间很短:比如函数内部声明的变量,或者块级作用域中的变量,当函数或者代码块执行结束时,作用域中定义的变量就会被销毁。因此这一类对象一经分配内存,很快就变得不可访问;
  2. 不死的对象,会活得更久:比如全局的 window、DOM、Web API 等对象。

两个垃圾回收器

V8 的垃圾回收策略,就是建立在代际假说的基础之上的。

在 V8 中,会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,新生代通常只支持 1~8M 的容量,老生代中存放生存时间久的对象。

  • 主垃圾回收器 -Major GC:主要负责老生代的垃圾回收。
  • 副垃圾回收器 -Minor GC (Scavenger):主要负责新生代的垃圾回收。

副垃圾回收器

新生代中的垃圾数据用 Scavenge 算法来处理。

Scavenge 算法

所谓 Scavenge 算法,是把新生代空间对半划分为两个区域,一半是对象区域 (from-space),一半是空闲区域 (to-space),如下图所示:

垃圾回收过程:

  1. 首先要对对象区域中的垃圾做标记;
  2. 标记完成之后,就进入垃圾清理阶段。
  3. 副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来。
  4. 完成复制后,对象区域与空闲区域进行角色翻转。

角色翻转示意图:

新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。副垃圾回收器会采用对象晋升策略,移动那些经过两次垃圾回收依然还存活的对象到老生代中。

主垃圾回收器

主垃圾回收器是采用标记 - 清除(Mark-Sweep)的算法进行垃圾回收的。

老生代中的对象除了新生代中晋升的对象,还有一些大的对象会直接被分配到老生代里。

两个特点:

  • 对象占用空间大
  • 对象存活时间长

标记 - 清除(Mark-Sweep)

工作过程:

  1. 标记过程阶段:标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
  2. 垃圾的清除过程:主垃圾回收器会直接将标记为垃圾的数据清理掉。

标记清除过程示意图:

标记 - 整理(Mark-Compact)

对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是又引入了另外一种算法——标记 - 整理(Mark-Compact)

工作过程:

  1. 先标记可回收对象:这个算法的标记过程仍然与标记 - 清除算法里的是一样的
  2. 让所有存活的对象都向一端移动,然后直接清理掉这一端之外的内存。

标记整理过程示意图:

拓展阅读

以上是关于图解 Google V8 # 20 :垃圾回收:V8的两个垃圾回收器是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

图解 Google V8 # 22 :关于内存泄漏内存膨胀频繁垃圾回收的解决策略(完结篇)

图解 Google V8学习笔记合集 23 篇(完结)

V8 JavaScript引擎研究垃圾回收器的实现

深入理解V8的垃圾回收原理

「译」Orinoco: V8的垃圾回收器

V8 堆栈空间和垃圾回收机制