JVM系列(十八) 垃圾收集之并发标记算法-三色标记法

914 阅读6分钟

1.为什么存在三色标记法

我们进行垃圾回收的时候,首先要知道什么是垃圾,普遍的两种判断垃圾的方法就是

  • 计数器法,无法解决循环依赖
  • 根可达法,从根找所有不可达对象都是垃圾,主流的垃圾寻找方法

那既然根可达法是主流的垃圾寻找方法,他具体是怎么实现的呢? 根可达法是从GCRoots 节点开始,使用 「标记 - 清除」 算法去实现,实现方案分为两个阶段

  • 标记阶段,在标记阶段,它从 GCRoots 节点开始扫描整个引用链,找到所有可达的对象
  • 清除阶段,在清除阶段,扫描整个引用链的不可达对象,然后将垃圾对象清除

但是这种方法有一个很大的问题,就是在标记的过程中,会不产生新的垃圾,就好像你妈妈在扫地的时候,你在嗑瓜子,扔瓜子皮,扫一堆,你扔一堆,垃圾仍然在不断的产生中,为了避免这种问题,整个应用程序都要为GC垃圾标记让路,整个程序必须停止Stop The World 简称STW,这就导致GC线程工作是无法并行的,GC停顿时间很长

为了解决这个问题,才有了三色标记法

2.三色标记法

三色标记法是一种垃圾回收法,它可以让JVM不发生,或者短时间发生STW(Stop The World),就能达到清除JVM内存垃圾的目的。JVM中的CMS、G1垃圾回收器所使用垃圾回收算法才是三色标记法,这也是为什么说CMS是第一个真正意义上的可以和用户程序并行处理垃圾回收的垃圾收集器,

三色标记算法思想三色标记法将对象的颜色分为了黑、灰、白,三种颜色

  • 白色,该对象尚未被垃圾收集器访问过,在可达性分析开始的阶段,所有的对象都是白色
  • 灰色,该对象已经被垃圾收集器访问过,但是这个对象直接引用的对象还没有被完全访问(至少存在一个引用未被扫描过)
  • 黑色,该对象已经被垃圾收集器访问过,并且该对象的所有引用对象都已经完全访问,所有的引用都被扫描过

程序初始状态,所有的对象都是白色的,只有 GC Roots 是黑色的

image.png

3.三个标记法实现过程

仅仅把对象划分为3种颜色,远远不够,三个标记法真正的牛逼之处在于其实现过程,在实现根可达性算法的时候,三色标记法把整个实现过程划分成了2个阶段

  1. 初始标记
  2. 并发标记
3.1 初始标记

初始标记阶段,初始 所有对象都是白色, 我们将 GCRoots 直接引用的节点,将它们标记为灰色,这个阶段需要STW(Stop the World)

image.png

3.2 并发标记

并发标记阶段,指的从上一步GCRoots的灰色节点开始,去扫描整个引用链,然后将它们标记为黑色,这个阶段不需要STW(Stop the World)

  • 如果该节点没有子节点,直接标记为黑色,因为他及它的引用对象已经被完全扫描,标记黑色
  • 如果该节点有子节点,则把当前节点标为黑色,它的子节点需要设置为灰色,表示该节点完全扫描,但是子节点还没有被完全访问
  • 重复这个过程,进行多次标记过程, 直到没有任何灰色的对象,才结束

第一次 并发标记 黑色A D,灰色E image.png

第二次并发标记 黑色E,灰色F G image.png

第三次并发标记 黑色F G image.png

三色标记法 结束后,我们分析下现在内存中的对象

  • 内存中不存在灰色对象
  • 黑色全都是可达对象,要保留的 A/D/E/F/G
  • 白色全部是不可达对象,就是垃圾要被回收的 B/C/H

4.三色标记算法的缺陷

三色标记算法也存在缺陷,在并发标记阶段的时候,因为用户线程与 GC 线程同时运行,有可能会产生多标或者漏标

4.1 多标-产生浮动垃圾

多标问题指的是本次GC应该回收的对象,却被多余地标记为黑色,标为为存活对象,从而导致该垃圾对象没有被回收。 为什么会出现多标的问题? 我们都知道,三色标记法在并发标记阶段,有可能之前已经被标记为存活的对象,其引用被删除,从而变成了不可达对象。

下面我们详细讲解一下多标,例如下图中

  • 此刻GC三色标记遍历到了节点 E,E对象为灰色
  • 稍后应用程序设置 E对象为 null,那么之后,对象 E变成垃圾了
  • 按道理 E是垃圾, 那么 E/F/G 全都是不可达, 全都要回收的
  • 但是此刻节点 E 已经是灰色的,那么下一轮后 E、F、G 节点都会被标记为存活的黑色状态
  • 这就导致E/F/G 没有被回收,就产生了多标问题
  • 这些垃圾就叫做浮动垃圾

image.png

多标就是本应该回收的对象在本次GC过程中没有被回收,它不会影响程序运行的正确性,多余的垃圾 在下一轮GC垃圾回收的时候就会被清理掉,所以说多标虽然有影响,但是影响不大,不会产生致命的错误

4.2 漏标-存活对象被回收(致命)

漏标问题指的是原本应该被标记为存活的对象,被遗漏标记为白色色,从而到时候回收白色对象的时候,把本来应该是黑色使用的对象,错误的回收掉了,与多标不同,漏标就非常严重,它会导致程序直接出错,影响程序的运行,漏标发生的两个必要条件

  1. 灰色对象断开了白色对象的引用
  2. 黑色对象重新和白色对象建立了引用关系

下面我们详细讲解一下漏标,例如下图中

  • GC线程此刻已经到了E,此刻E变灰色
  • 然后E对象设置为null,灰色的E 断开引用G
  • 此刻G就是白色 不可达的对象,就是垃圾,需要被回收
  • 下一刻设置 D.obj= G,这样建立D到G的引用关系
  • 这时候 D/G之间是有引用关系的,所以G 应该是黑色
  • 但是对象D 已经遍历设置完黑色了,不会再对他二次遍历
  • 所以D/G 之间的引用关系是无法知道的, 这就导致G节点一致是白色
  • GC回收的时候,白色的G被回收,其实G对象是有引用,应该是黑色
  • 这就是漏标问题

image.png


至此 我们三色标记法的原因及三色标记法的原理和它的优势劣势已经讲解清楚了,我们必须知道三色标记法的问题,才能有针对性的解决这种问题