CMS底层三色标记

79 阅读5分钟

CMS底层三色标记

三色标记

  • 在并发标记过程中,因为在标记期间用户线程还在继续跑,对象间的引用状态可能发生了变化多标和漏标的情况有可能发生

  • 问题: 什么是三色标记

    • 黑色: 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过灰色对象) 指向某个白色对象

    • 灰色: 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过

    • 白色: 表示对象尚未被垃圾收集器访问过,显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达

    • 简单理解

      • 黑色对象表示自身和成员被垃圾收集器检查完了
      • 白色对象表示没有被垃圾收集器检查
      • 灰色对象表示自身被垃圾收集器检查了,但是成员没被检查完

多标-浮动垃圾

  • 问题: 什么是浮动垃圾

    • 在并发标记的过程中,由于用户线程还在进行着,可能出现已经标记的对象的状态发生了改变,这些对象可能已经是垃圾对象了,但是状态还没改变,也就是本应该回收的但是没有回收到的内存
    • 在并发标记和并发清理开始后产生的新对象,通常的做法是直接全部当成黑色本轮不进行清除,在并发标记阶段新增对象可能在重新标记阶段期间可能也会变成垃圾,这也算是浮动垃圾的一部分
  • 问题: 如何解决浮动垃圾

    • 浮动垃圾并不会影响垃圾回收的正确性,只需要等到下一轮垃圾再回收即可

漏标-读写屏障

  • 问题: 什么是漏标

    • 之前被扫描完的对象建立了新的引用关系,也就是黑色对象建立了新的引用关系,比如说执行过程中,新生成了一个对象,然后被已经标记的对象引用,但是这个新生成的没有标记
    • 漏标会导致被引用的对象当成垃圾误删
  • 问题: 如何解决漏标

    • 增量更新

      • 黑色对象新增白色对象引用关系之后,会将这条关系记录下来,等并发扫描结束后,再以黑色对象为根重新扫描一次
      • 可以简单理解为:黑色对象回退到灰色,重新扫描一次
    • 原始快照(SATB)

      • 灰色对象要删除白色对象的引用关系时,将这条关系记录下来,等并发扫描结束后,再以灰色对象为根扫描一次,这样就能扫描到白色对象,将白色对象直接标记为黑色,目的就是让这种对象在本轮GC中存活下来,等待下一轮GC
  • 读写屏障并发像并发编程中的内存屏障,更像是SprIng中的AOP,增量更新或内存快照记录数据时,就是在业务代码前后执行插入记录的方法

  • 并发标记时对漏标的处理方案

    • CMS: 写屏障 + 增量更新
    • G1: 写屏障 + 原始快照
    • ZGC: 读屏障 + 原始快照
  • 问题: 为什么G1用原始快照,CMS使用增量更新呢

    • G1采用原始快照相对于增量更新效率会更高,当然原始快照可能造成更多的浮动垃圾,因为不需要在重新标记阶段再次深度扫描被删除的引用关系
    • 而CMS对增量引用的根对象会做深度扫描,当然G1的很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话,G1维护的成本更加高,所以G1选择原始快照不做深度扫描而是简单标记,等到下一轮GC再深度扫描

卡表和记忆集

记忆集和卡表.png

  • 问题: 如果年轻代某个对象存在跨代引用,难道又把老年代扫描一遍吗

    • 老年代堆被划分成以512字节大小的一块块内存空间,也就是卡页,每块内存空间有很多对象,这个卡页的状态以及它的起始地址被维护在年轻代的一块内存空间卡表里,它是一个字节数组,只要有卡页里的对象存在跨代引用,卡表里对应的元素的标记位会变成1,而这个存在跨代引用的卡页里的所有对象都会被加入到grroots的扫描范围中
  • 卡表和记忆集就是为了解决跨代引用的,G1收集器几乎在每个小的region区都有个卡表记录跨代引用的对象

  • hotspot使用写屏障维护卡表状态. ··