Java基础之《JVM性能调优(11)—JVM垃圾标记》

一、垃圾标记经典算法三色标记法

1、3色标记法
就是用3种颜色来标记对象
1)白色:未被标记的对象
2)灰色;自身被标记,成员变量未被标记
3)黑色:自身和成员变量都已标记完成(代表存活对象)

2、垃圾回收算法的思路
垃圾回收算法的思路,就是标记和回收。
例如:常规的,标记-清除、标记-复制、标记-整理。整个流程就是先标记,再回收。
1)标记:识别出哪些对象存活?哪些对象是垃圾(可回收)?
2)回收:回收方式(清除、复制、整理),或转移对象(复制、整理)。
清除垃圾,复制存活对象,整理内存碎片

3、标记的难题
标记最大的难题就是边标记垃圾,边生产垃圾,即并发标记。

并发标记会产生2个问题:浮动垃圾和漏标
1)多标-浮动垃圾
在并发标记的时候,标记了GCRoot这个对象为起点向下搜索引用的对象,这个时候栈帧出栈了,那么其引用的对象之前已经标记为非垃圾对象,浮动垃圾下次再收集。
比如:栈帧 --引用对象A --引用对象B
GC线程从GCRoot开始标记,标记到对象B结束。认为A、B是活对象。突然间应用线程把栈帧出栈了。
2)漏标
由于并发的原因,原本是存活的对象,却被GC线程回收了。

4、JVM采用了3色标记法,解决标记的2大难题
步骤1:初始化阶段
初始化阶段,所有对象都是白色,并记录在白色集合里面。

步骤2:处理GCRoot直接引用对象
把GC Roots直接引用到的A、B对象挪到灰色集合中

步骤3:
将灰色集合的A、B挪到黑色集合中,然后把A、B引用的其他对象(C),全部挪到灰色集合中。

递归将灰色集合的C挪到黑色集合中,然后把C引用的其他对象D、E全部挪到灰色集合中。

递归将灰色集合的D、E挪到黑色集合中,由于D、E没有其他引用的对象,故标记结束。

经过以上的标注后,黑色集合A、B、C、D、E为存活对象,白色集合F、G、H为不可达对象(可回收对象)

5、小结
白色集合的对象即为GC Roots不可达,可以进行回收。
标记完绝对没有灰色,因为灰色是中间状态。
注:如果标记结束后对象仍为白色,意味着已经“找不到”该对象在哪了,不可能会再被重新引用。
当Stop The World(STW)时,对象间的引用是不会发生变化的,可以轻松完成标记。
而当需要支持并发标记时,即标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。

二、为什么会出现浮动垃圾

1、背景

1)gc线程,先执行标记
2)应用线程,执行代码objB.fieldC = null
按道理执行了objB.fieldC = null后,C、D、E是要被当做垃圾回收的。
但是gc线程已经先行一步,把C标记为灰色了,一旦是灰色就会继续递归执行3色标记法,最终是C、D、E被当做存活对象(其实是垃圾),这种对象称之为浮动垃圾。
浮动垃圾其实不会影响应用程序的运行,它只是暂用了堆内存而已,只能等到下一轮gc才能被识别到。

三、什么是垃圾漏标?为什么会产生漏标?

1、背景

1)gc线程,先执行标记
2)应用线程,执行代码
var e = objC.fieldE; //读取
objC.fieldE = null; //灰色C断开引用白色E
objB.fieldE = e; //黑色B引用白色E

只要应用线程执行代码后,C就没有E的引用了,故E不会继续被标注。
但是问题来了,代码objB.fieldE = e,变成了黑色B引用白色E,由于B是黑色的,不会再重新做遍历处理。
最终导致的结果是:E一直停留在白色集合中,最后被当做垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的。

不难分析,漏标只有同时满足以下两个条件时才会发生:
1)灰色对象断开了白色对象的引用(直接或间接的引用);即灰色对象原来成员变量的引用发生了变化。
2)黑色对象重新引用了该白色对象;即黑色对象成员变量增加了新的引用。
以上这种现象就是漏标。

四、写屏障+增量更新,解决漏标的问题(CMS技术方案)

1、什么是增量更新

当应用线程执行了objB.fieldE = E代码时,触发写屏障:
1)在并发标记阶段过程中,gc线程基于写屏障把E存储在集合中。
2)在最终标记阶段过程中,gc线程从集合中,把E捞出来重新扫描,把源头B重新设置为灰色。
3)重新进入并发阶段后,gc线程再把灰色的B重新设置为黑色。
以上3个过程,就是增量更新。
增量更新解决了漏标的问题。
CMS采用增量更新来解决漏标问题。

五、写屏障+SATB,解决漏标的问题(G1技术方案)
SATB的全称是Snapchat At The Beginning,原理是,当GC开始之前,复制一份引用关系快照,即当成员变量的引用改变时,记录该成员旧的引用对象,保存到satb_mark_queue中。

把E存起来是增量更新,把objC.fieldE存起来是SATB
例如,上图中,当对象C、B的成员变量E改变时,采用写屏障把对象E记录到satb_mark_queue队列中。
每条GC线程都自带一个satb_mark_queue队列,在并发阶段会处理satb_mark_queue中的对象,处理的方法是把satb_mark_queue队列中的对象当做根重新扫描一遍,以解决白色对象引用被修改产生的漏标问题。
缺点:
如果被修改引用的白色对象(例如E对象)就是要被收集的垃圾,SATB的标记会让它躲过GC,这就是浮动垃圾。因为SATB的做法精度比较低,所以造成的浮动垃圾也会比较多。

六、G1的SATB与CMS的增量更新有什么区别

1、CMS的增量更新,是把黑色对象设置为灰色,结果就是会导致在【并发标记周期的remark阶段】重新扫描整个堆重新标记,最终导致CMS remark有可能会非常慢。

2、G1 SATB的做法就是在变更前把旧对象记录下来,每条GC线程都自带一个satb_mark_queue队列,在并发阶段会处理satb_mark_queue中的对象,处理的方法时把satb_mark_queue队列中的对象当做根重新扫描一遍,以解决白色对象引用被修改产生的漏标问题。
这样的好处就是快,不需要像CMS那样remark阶段整个堆扫描一遍。
SATB虽然浮动垃圾多,但是young gc每次都会自动清理掉的。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值