JVM系列(十九) 垃圾收集之三色标记法-漏标问题解决方案

1,249 阅读7分钟

1.三色标记法缺陷-漏标问题

我们上一篇文章知道了三色标记法存在多标和漏标的问题,多标无伤大雅,漏标才是致命的,对于漏标问题,有什么解决办法呢? 我们知道漏标的两个必要条件

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

从代码角度来看,这两个条件其实就是 三步操作

#1.读取A对象的属性 field 的引用值,就是 B,就是灰色的A
var B = objA.field //读
#2.断开A对象的引用BA对象的属性field 设置为空,断开与B的链接,B是不可达的垃圾
objA.field = null //写
#3.有个已经扫描过的C对象 重新链接了BB可达,真实有用对象
objC.field = B //写

那我们是不是破坏这三步中的任何一步,漏标问题的条件就不满足了,问题就不会发生了,比如我们把漏标断开的对象记录下来,记录这些改变的对象,记录完毕后,再次对这些对象重新遍历下,标记下状态,这个过程就叫做重新标记, 重新标记是需要STW的,我们只是重新遍历那些改动的对象,大大的缩短了STW的时间

要破坏这三部中的任何一步,就产生了读写屏障

  • 读屏障 拦截第一步,读的过程
  • 写屏障 拦截第二步,第三步,写入的过程
  • 读写屏障拦截的目的很简单,就是在操作对象断开/链接的过程中, 把操作变更的对象记录下来

2.解决方案

读写屏障拦截的目的很简单,就是在操作对象断开/链接的过程中, 把操作变更的对象记录下来

2.1 读屏障 + 重新标记

读屏障就是直接针对第一步,在读取节点之前进行操作

  • 读屏障,在读之前记录下了要读取的对象
  • 条件2 黑色对象重新链接,引用了白色的对象
  • 在重新引用的前提就是 我们可以获取到白色对象,在获取白色对象的时候,读屏障就发挥作用了
  • 我们把这个对象记录下来,等待并发标记结束后STW
  • 然后针对所有记录下来的对象,重新标记,重新扫描
  • 重新标记的过程一定要STW,否则重新标记完,又有新的变化,无穷无尽
2.2 写屏障 + 增量更新

CMS解决漏标问题就是采用增量更新, 什么是增量更新?

  • 增量更新就是当黑色对象 链接 新的 白色对象,重新建立引用关系的时候, 就将这个插入新链接的引用记录下来
  • 把这些引用记录全都存下来,等待遍历
  • 并发标记扫描结束后, 将这些记录过的引用关系中的黑色对象为根, 重新扫描遍历一次
  • 黑色对象一旦新建立了链接,存在新的白色对象的引用之后, 它就变回灰色对象了
  • 破化第二个条件,因为黑色对象重新引用了白色对象,并且记录下来重新标记
  • 从而保证不会漏标
  • 这样避免了漏标问题,但是重新标记阶段必须STW,会导致GC时间变长

CMS破坏的就是第二个条件-> 黑色对象重新和白色对象建立了引用关系

建立链接后重新记录下来,太卡有一个缺点就是因为要对这些关系重新扫描这些黑色的对象,所以会稍微耽误一点时间,但是这点时间相对于 如果你要并发标记整个链路,还是微不足道的,可以接受的,实际的项目中,这瞬间引用关系发生变化的对象还是很少的

2.3 写屏障 + 原始快照(SATB)

G1解决漏标问题的方案就是-原始快照,什么是原始快照?

  • 原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个删除链接的引用记录下来
  • 这样破坏的是第一个条件,因为灰色对象断开了链接,但是这个删除链接的关系被记录下来
  • 尝试保留开始时的对象图,即原始快照(Snapshot At The Beginning,SATB)
  • 并发扫描结束之后, 再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次
  • 这样后续在扫描的过程中,始终都是按照存在这个被删除的关系,即使D 删除 了G,但是原始的对象快照图中 D依旧和G保持着关系,所以G不会被当作垃圾处理
  • 这样就能扫描到白色的对象G,将白色对象直接标记为黑色
  • 目的就是让这种被删除的对象,依旧在本轮gc清理中能存活下来
  • 当然这个对象也有可能真的是垃圾,浮动垃圾 ,待下一轮gc的时候重新扫描就会被清除

G1 垃圾回收器采用的是原始快照STAM的方案,破坏第一个条件->灰色对象断开了白色对象的引用

我们断开链接前保存快照,这样在快照中,灰色对象和白色对象同样都是由引用关系,后面引用关系发生变化时,快照中引用关系时存在的,这样就不会发生真实对象被当作白色对象回收的问题

但是这种方式也有一个缺点,就是如果这个对象,断开连接后,没有新的对象建立链接,那么他就是垃圾对象,但是快照中又存在引用,导致该垃圾对象不会被回收,就产生了浮动垃圾,相对于致命的缺点存活对象被回收,浮动垃圾相比之下也是可以接受的,因为浮动垃圾伴随着下一次的GC,就会被清楚掉

3.CMS和G1全面对比

CMS增量更新对比G1原始快照,到底哪种方案更好? 我们查阅了一些资料,对比之下G1的解决方案更加优秀,全面对比了两种垃圾收集方法,总结原因如下:

  • GC耗时方面
    • 因为两种都要对 记录下来的对象进行重新扫描,所以这块两种垃圾处理器差别不大
  • STW时间
    • CMS号称响应优先处理器,以最小停顿时间为目标
    • G1则是以垃圾回收优先,在规定时间内完成性价比最高的回收
  • 垃圾碎片
    • CMS采用标记清除法,会产生内存碎片
    • G1采用标记整理法,对内存空间进行整理,没有内存碎片
  • 回收垃圾区域
    • CMS回收老年代,搭配新生代ParNew一起使用
    • G1回收老年代及新生代,独立使用
  • 垃圾回收算法
    • CMS 过程分为 1.初始标记->2.并发始标记->3.重新标记->4.并发清除
    • G1 过程分为 1.初始标记->2.并发始标记->3.最终标记->4.筛选回收

至此,我们针对三色标记的缺陷漏标问题,给出了完美的解决方案,读屏障+重新标记,或者写屏障+增量更新,或者写屏障+原始快照这几种解决方式,CMS垃圾收集器采用的就是写屏障+增量更新,G1采用的是写屏障+STAB