详细图解JVM三色标记法

三色标记法是JVM中用来标记对象是否为垃圾的一种方法,主要是针对CMS、G1等垃圾收集器使用的,这类收集器都有一个垃圾回收线程与用户线程同时执行的并发过程,这是一般标记清除算法不能支持的。

为什么要使用三色标记法?

三色标记法就是为了使垃圾扫描阶段能够与用户线程并发执行而产生的,因为传统的标记清除算法,必须要暂停所有用户线程。

传统的标记清除算法下,只有两个状态位0、1,比如0:表示未标识,1:表示对象可达,一次扫描后结束清除所有状态为0的,然后再把状态为1的重置为0。

但是如果与用户线程同时执行就不能这样玩了,因为一旦同时执行就有可能在一条链未全部扫描完的情况下,用户线程改变了这条链上的引用关系,比如现在有一条引用链:A—>B—>C—>D,当扫描到C时,A和B都被标识为1,此时C到D的引用关系被删除,那么D对象就不能确定是否为垃圾对象,因为有可能D又被用户线程设置为其他对象的引用了,那么为了D不被误删,只能让D的标识也为1,但是如果D就是没有被其他对象引用了,那么D就逃过了这次垃圾收集的过程,这就会造成大量的浮动垃圾。

当然肯定也不能设置为0,因为0在未扫描之前虽然表示的是未标记对象,但是在扫描开始后就表示垃圾对象了。

所以上述问题很明显就是缺少了一个表示中间状态的过程,由于线程同时进行,所以引用链上的对象并不是简单的可达与不可达的关系,而是会有一个扫描过程中的状态,所以就出现了三色标记法。

三色标记法中的三色

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

灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过,也就是整个引用链还未全部扫完。

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

初始阶段:全部为白色
在这里插入图片描述

A对象扫描完成后变为黑色,B对象正在扫描则标识为灰色,剩余的白色对象标识还未被扫描。
在这里插入图片描述
最终按照可达性分析算法一轮扫描下来结果如下
在这里插入图片描述

最终白色对象E即为垃圾对象。

主要就是利用三个集合,分别来存放三种颜色的对象,开始扫描时把被扫描的白色对象从白色集合中移动到灰色集合中,灰色对象扫描完成后,又被移动到黑色对象集合中,最终完成所有初始标记时识别到的GCRoot引用链路径后,余下的白色集合中的对象即为垃圾对象。

三色标记的漏标问题

三色标记的思想非常简单,但仔细分析一下就会发现其中的问题,如果把一个白色对象的引用设置到一个黑色的对象上,那么这个白色对象就会被错误的认为是一个垃圾对象,因为黑色对象表示的是这个对象已经完成了扫描且这个对象的所有引用都已经扫描过。

第一次标记时,关系如下:

在这里插入图片描述
用户线程修改了引用关系如下:
在这里插入图片描述
此时接着扫描E对象,发现E对象之后没有引用关系了,把E对象设置为黑色,垃圾收集器认为两条引用链上的对象全部扫描完毕,但是F对象却被遗漏了。

Wilson于1994年在理论上证明了,当且仅当以下两个条件同时满足时,会产生“对象消失”的问 题,即原本应该是黑色的对象被误标为白色:

1、赋值器插入了一条或多条从黑色对象到白色对象的新引用;

2、赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

如何解决漏标问题?

既然问题的产生需要同时满足上述两个条件,那么要解决就只需破坏其中一种即可,CMS和G1恰好分别利用其中一种条件来解决。

CMS 增量更新(Incremental Update)

增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象 了。

C对象被修改为灰色,那么就会沿着灰色对象继续扫描,最终会扫描到F对象。
在这里插入图片描述

G1 原始快照(Snapshot At The Beginning, SATB)

原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来 进行搜索。

第一、二两条链扫描完成后,多出了第三条引用链,从之前的灰对象E开始,指向F对象,这样F对象就不会被清理掉了。
在这里插入图片描述

使用这种方式会有一个问题,假设引用关系如下:

在这里插入图片描述
之后引用关系被改变
在这里插入图片描述
E到F的引用没有了,F也没有再被其他对象引用,但是由于E对象为灰色对象,所以为了避免漏标,E对象最终还是会有一条到F的引用关系,这就是浮动垃圾问题,F对象会逃过本次的垃圾扫描,等待下次再被清理,但这总比漏标要好的多。

对比文章一开始提到的标记清除方法下产生的浮动垃圾问题,这种情况要少很多,因为只有在改变灰色对象时才需要记录,而如果用标记清除那么只要在垃圾回收期间改变了对象的关系都必须定义为非垃圾对象。

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java虚拟机(JVM)内存模型是JVM用于管理Java程序运行时内存的一种机制。JVM将内存划分为不同的区域,每个区域都有自己的用途和生命周期。这些区域包括: 1. 程序计数器(Program Counter Register):程序计数器是一块较小的内存区域,它用于记录当前线程所执行的字节码指令的地址。每个线程都有一个独立的程序计数器。 2. Java虚拟机栈(Java Virtual Machine Stacks):Java虚拟机栈是线程私有的,它用于存储Java执行时的局部变量、操作数栈、动态链接、方出口等信息。 3. 本地方栈(Native Method Stack):本地方栈与Java虚拟机栈类似,但是它是为本地方服务的。 4. Java堆(Java Heap):Java堆是Java虚拟机管理的内存中最大的一块。Java对象实例和数组都在堆上分配内存。堆是所有线程共享的一块内存区域。 5. 方区(Method Area):方区是用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 6. 运行时常量池(Runtime Constant Pool):运行时常量池是方区的一部分,用于存放编译器生成的字面量和符号引用。 7. 直接内存(Direct Memory):直接内存不是JVM运行时数据区的一部分,但是它也可以被JVM所管理。在使用NIO(New IO)时,可以使用直接内存来提高IO性能。 以上就是JVM内存模型的主要区域。JVM内存模型的划分对于Java程序的运行和调优都非常重要。在实际开发中,需要根据具体情况调整JVM内存的配置,以达到更好的性能表现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值