深入浅出垃圾回收机制-新老生代垃圾回收

781 阅读3分钟

大噶好,我系找不到实习的又想找实习的二五仔。

楼主最近准备跑路,就去找学长帮咱模拟面试。

学长:“你八股都会吧。”

我:“会的会的。”

学长:“那常规的我就不问了,V8的垃圾回收了解吧?”

我:“了解的,有引用计数和标记删除,引用计数由于循环引用的问题基本没有使用了,现在我们用的都是标记删除。”

学长:“嗯,那你知道新生代和老生代的标记删除有什么不同么?”

我:“唔........”

“好像不太了解”

“这有篇文章,回去好好看看”

被狠狠的羞辱之后仔细的了解了一下:

Chrome 的垃圾回收算法

首先,为了提高垃圾回收效率,V8 将堆分为两类新生代和老生代。

两者区别:

新生代:

  1. 存活周期短,大部分在经过一次的垃圾回收之后,内存就会被释放掉
  2. 通常只支持 1~8M 的容量
  3. 回收方式:副垃圾回收器 - Scavenge

老生代:

  1. 存活周期很长,一直是活跃的对象,不需要被回收
  2. 容量大
  3. 回收方式:主垃圾回收器 - Mark-Sweep & Mark-Compact

新生代垃圾回收器 - Scavenge

背景:

在 JS 中,大多数分配了新内存的对象,都会先放到新生代垃圾回收器里面,我们知道新生代的内存容量有限,所以体积过大的对象会直接存放到老生代回收器里面。

特点:

用空间换时间。

回收过程:

Scavange算法将新生代堆分为两部分,分别叫from-space和to-space

  1. 标记活动对象和非活动对象
  2. 复制 from space 的活动对象到 to space 并对其进行排序
  3. 释放 from space 中的非活动对象的内存
  4. 将 from space 和 to space 角色互换

如何区分活动对象

V8 是由 C++ 实现的,存在一个根指针,然后会进行深度优先遍历,被搜索到的子节点说明该节点的引用对象可达,并为其留下标记,然后递归这个搜索的过程,直到所有子节点都被遍历结束,那么没有被标记的对象节点,说明该对象没有被任何地方引用,可以证明这是一个需要被释放内存的对象,可以被垃圾回收器回收。

新生代变为老生代过程

新生代堆分为:nursery子代/intermediate子代,最开始进来的变量在 nursery子代,经过一次垃圾回收流程,并且没有被垃圾回收的话,我们就将其从 nursery子代 移动到 intermediate子代。再经过一次垃圾回收流程,并且没有被垃圾回收的话,我们就将其从 intermediate子代 移动到 老生代。

老生代垃圾回收

Scavenge的问题

  1. 容易造成大量内存浪费
  2. 复制效率低

老生代垃圾回收包含了 “标记清除” 和 “标记整理” 算法

标记清除

标记清除算法和 Scavenge 非常类似,只不过并没有移动删除,而是发现未引用,就在原位置立刻删除。

  1. 标记活动对象和非活动对象
  2. 释放 from space 中的非活动对象的内存

这就会造成一些问题,比如 会空出很多未使用的内存位置,我们称呼其为内存碎片,而这些内存位置并不连贯,所以我们需要做“标记整理”,使得内存可以再次利用。

标记整理

在标记整理过程中,V8会将所有的活动对象往一端移动,移动完成后,直接清理掉边界外的内存。 从而使得内存连贯使用。