JVM三色标记

在CMS和G1并发标记的过程中会采用三色标记算法,它是用来解决 GC 运行时程序长时间挂起的问题,最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个 GC。
三色标记法很简单。首先将对象用三种颜色表示,分别是白色、灰色和黑色。
首先会把所有的对象都至为白色,然后根据跟可达性算法开始扫描,把扫过的对象变成灰色,然后继续扫描他的子对象,这个时候把子对象变成灰色,把这个对象变成黑色。等到扫描完成的时候把子对象也变成黑色。没有扫到的对象就还是白色了,那么白色的对象就是需要清理的对象。
黑色:根对象,或者该对象与它的子对象都被扫描过。
灰色:对本身被扫描,但是还没扫描完该对象的子对象。
白色:未被扫描对象,如果扫描完所有对象之后,最终为白色的为不可达对象,既垃圾对象。
在这里插入图片描述

三色标记会出现的问题
GC 并发情况下的漏标问题
为什么 会出现漏标问题呢,比如上面那个图吧,
线程1已经全部扫描完成了,线程2运行到B节点的时候挂起了 ,这个时候呢,程序又把c的引用的线程1 的A,把B和c之间的引用至为null,然后等到线程2唤醒的时候发现B的引用没有了,线程1又扫描完了,这个时候c对象也不会被变成灰色或者黑色了,就还是会白色的被当初垃圾对象给清除掉了。
在这里插入图片描述
那么这个漏标问题CMS和G1是怎么解决的呢?

先说CMS的解决方案:
cms采用的是Incremental Update 算法,简单的意思就可以被理解为,如果线程1扫描完了,但是c被线程1的A引用了,这个时候需要再从头重新扫描一次,就是增加就更新。

在这里插入图片描述
增加
在这里插入图片描述
重新扫描后把c也更新为黑色

G1 中的解决方案
SATB(snapshot-at-the-beginning)简称快照
那么什么是快照呢,意思就是说当GC扫描的时候会先拍一张照片放到GC的堆栈,然后扫描完成的时候再拍一张照片,两张照片再对比一下看看,如果引用的关系被删除了,则把这个引用从GC的堆栈里再给这个引用被删除的对象。这样就不会被当成垃圾对象清理了
在这里插入图片描述
最开始的快照。
在这里插入图片描述
B和c之间的引用被删除了
但是要把这个引用推到GC的堆栈,保证白色的c还能被GC扫描到在这里插入图片描述

对比
SATB 算法是关注引用的删除。(B->C 的引用)
Incremental Update 算法关注引用的增加。(A->C 的引用)
G1 如果使用 Incremental Update 算法,因为变成灰色的成员还要重新扫,重新再来一遍,效率太低了。 所以 G1 在处理并发标记的过程比 CMS 效率要高,这个主要是解决漏标的算法决定的。

G1 中的技术细节
跨代引用
在这里插入图片描述
图中的X引用和Y引用就被称为跨代引用。

堆空间通常被划分为新生代和老年代。由于新生代的垃圾收集通常很频繁,如果老年代对象引用了新生代的对象,那么回收新生代的话,需要跟踪从老 年代到新生代的所有引用,所以要避免每次 YGC 时扫描整个老年代,减少开销。

RSet(记忆集)
记录了其他 Region 中的对象到本 Region 的引用, RSet 的价值在于使得垃圾收集器不需要扫描整个堆,找到谁引用了当前分区中的对象,只需要扫描 RSet 即可。 RSet 本身就是一个 Hash 表,如果是在 G1 的话,则是在一个 Region 区里面。
CardTable (卡表)
由于做新生代 GC 时,需要扫描整个 OLD 区,效率非常低,所以 JVM 设计了 CardTable,如果一个 OLD 区 CardTable 中有对象指向 Y 区, 就将它设为 Dirty (标志位 1), 下次扫描时,只需要扫描 CARDTABLE 上是 Dirty 的内存区域即可。 字节数组 CARDTABLE 的每一个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块被称作“卡页”(Card Page)。 一般来说,卡页大小 都是以 2 的 N 次幂的字节数,假设使用的卡页是 2 的 10 次幂,即 1M,内存区域的起始地址是 0x0000 的话,数组 CARD_TABLE 的第 0、1、2 号元素,分别 对应了地址范围为 0x0000~0x03FF、0x0400 ~ 0x07FF、0x0800~0x011FF 的卡页内存.

总结
这里描述的是 G1 处理跨代引用的细节,其实在 CMS 中也有类似的处理方式,比如 CardTable,也需要记录一个 RSet 来记录,我们对比一下,在 G1 中是每 一个 Region 都需要一个 RSet 的内存区域,导致有 G1 的 RSet 可能会占据整个堆容量的 20%乃至更多。但是 CMS 只需要一份,所以就内存占用来说,G1占用的内存需求更大,虽然 G1 的优点很多,但是我们不推荐在堆空间比较小的情况下使用 G1,尤其小于 6 个 G。

安全点与安全区域

安全点
用户线程暂停,GC 线程要开始工作,但是要确保用户线程暂停的这行字节码指令是不会导致引用关系的变化。所以 JVM 会在字节码指令中,选一些指令, 作为“安全点”,比如方法调用、循环跳转、异常跳转等,一般是这些指令才会产生安全点。 为什么它叫安全点,是这样的,GC 时要暂停业务线程,并不是抢占式中断(立马把业务线程中断)而是主动是中断。 主动式中断是设置一个标志,这个标志是中断标志,各业务线程在运行过程中会不停的主动去轮询这个标志,一旦发现中断标志为 True,就会在自己最近 的“安全点”上主动中断挂起。

安全区域
为什么需要安全区域? 要是业务线程都不执行(业务线程处于 Sleep 或者是 Blocked 状态),那么程序就没办法进入安全点,对于这种情况,就必须引入安全区域。 安全区域是指能够确保在某一段代码片段之中, 引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区看作被扩展拉伸了的安全点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值