性能文章>空中楼阁之纸上谈兵 我对cms的认识>

空中楼阁之纸上谈兵 我对cms的认识原创

3年前
705934

以下看法均基于jdk8

要说老年代之前,需要知道什么情况下对象进入老年代?
1、年轻代中年龄到了的对象,可以进入老年代
2、年轻代ygc后,剩余的活跃对象在survivor to区放不下,那么这部分活跃对象就需要进入老年代
3、大对象直接进入老年代(可以通过配置参数-XX:PretenureSizeThreshold=1000,单位字节,默认是0,意味着任何对象都会先在年轻代分配内存)
4、动态年龄判定:若年轻代年龄从0~age的对象大小和超过survivor to的一半,则对象年龄超过age的对象都可以进入老年代(可以通过配置参数-XX:TargetSurvivorRatio=50,默认值是50)


cms的全称是concurrent mark sweep,其分为foreground cms 和 background cms这两种。

一般我们所说的cms指的是background cms,其是对老年代进行垃圾回收,使用的算法是标记-清除,故会造成内存碎片。


1、先说说background cms的常说的几个阶段。
1.1、初始化标记:
干什么:从GC Roots开始标记直接关联的老年代对象,另外也需要以年轻代对象为根开始标记直接关联的老年代对象,故该阶段会扫描年轻代。
影响:会进行短暂的stw(stop the world)
备注:
为什么扫描年轻代?或者说为什么将年轻代的对象作为根开始标记呢?
若是一个老年代没有跟GC Roots直接关联,难道就是垃圾吗?显然不能这么理解,别忘记了还有年轻代呢。若是其被年轻代引用,老年代垃圾回收将其当垃圾回收掉了,那不就出大事了呀。

1.2、并发标记
干什么:以上一阶段标记的对象为起点,开始标记,直到标记结束。
如何解决并发标记:三色标记,白色为未开始的对象或结束后的垃圾,灰色为标记了一部分引用关系的对象,黑色则是标记了对象中所有关联的引用。
存在的问题:
1.2.1、错标:将垃圾标记成了黑色,最终导致本次不能被收集,这就是浮动垃圾,但可以下次再回收。
1.2.2、漏标:将不是垃圾的对象没有标记,最终作为垃圾给回收掉,这就问题大了。
解决方案:增量更新、原是快照
cms的解决方案:增量更新+写屏障

1.3、并发预处理
干什么:更新卡表
卡表是干嘛的:卡表记录了从老年代引用年轻代对象的关联关系。
年轻代的对象不仅仅被GC Roots所引用,还可能会被老年代所引用,当YGC时,若是先扫描一边老年代,那也太忙了,若是从卡表中就能很快知道,老年代中对象哪些引用了年轻代,这样YGC也能很快完成,从而减少开销。
为什么需要更新:上一阶段并发标记阶段,应用线程和GC线程是并发执行的,因此可能产生新的对象关系发生变化(如年轻代晋升到老年代、老年代对象的引用关系发生变更、直接在老年代分配对象等),卡表中关联关系有些已经变成

1.4、并发可停止的预处理
目的:选择适合的时机进入下一阶段
合适的时机是:一次ygc之后
为什么是一次ygc之后呢:下一阶段还需要重新扫描年轻代,若是提前ygc,那么剩余年轻代存活的对象是不是少多了,那接下来扫描是否耗时相对减少了
如何控制:本阶段最长等待时长内,没有ygc之后,就会进入下一阶段。另本阶段最长等待时长可配置-XX:CMSMaxAbortablePrecleanTime=5000,单位秒,默认是5秒

1.5、重新标记
关注点:
1.5.1、会重新扫描年轻代
1.5.2、不会重复标记,提高标记性能
影响:还会进行短暂的stw(stop the world),一般cms慢,这个阶段会很明显,当然也是一个调优方向。
优化:-XX:+CMSScavengeBeforeRemark,重新标记之前主动触发一次ygc

1.6、并发清除
1.7、并发重置

2、触发background cms gc的时机
2.1、老年代使用比例达到(可以通过参数-XX:CMSInitiatingOccupancyFraction=70配置)
2.2、手动gc并行化了(如主动调用System.gc,并且配置了-XX:+ExplicitGCInvokesConcurrent)
2.3、年轻代悲观策略
2.4、元空间发生了变化

3、foreground cms
整个gc阶段,都是stw的。另阶段同background cms差不多,但是没有并发预处理和并发可停止的预处理这两个阶段。
触发时机:
3.1、手动gc(如主动调用System.gc)
3.2、年轻代悲观策略
3.3、晋升失败(promotion fail)
3.4、并发模式失败(concurrent mode fail)

备注:
若是配置了进行整理,则会再进行多少次full gc之后才会来一次整理。(可以通过参数-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0配置,0表示每次都进行整理)
此处的full gc次数会在每次background cms之后被清空为0
当超过full gc的阈值,之后每一次都会触发整理的行为

4、上面的名词理解
4.1 年轻代悲观策略
理解:当老年代最大的连续内存低于当前年轻代对象大小,也低于年轻代历史晋升到老年代的对象大小的平均值,则会启用悲观策略
优化方向:碎片化了,增加整理动作

4.2 晋升失败(promotion fail)
理解:年轻代中对象进入老年代之前,会去看看老年代是否放得下,若是放不下,则就是晋升失败
优化方向:
4.2.1 是否提前进入老年代了?也就是年轻代小了
4.2.2 碎片化?是否老年代小了?或者整个堆小了?
4.2.3 是否对象产生快了?是否没有回收掉,有内存泄漏?(代码层面思考)

4.3 并发模式失败(concurrent mode fail)
理解:当正在进行background cms时,有对象进入老年代,正好老年代放不下,此时就是并发模式失败
优化方向:
4.3.1 触发background cms时机的比例是否合理?是否高了点?另cms gc速度是否慢了?增加并行?初始化并行,重新标记前先ygc?
4.3.2 其他同上4.2优化方向


参考资料:
https://mp.weixin.qq.com/s/vmnBlrM7pTtVuyQU-GTcPw cms gc的7个阶段
https://juejin.cn/post/6844903760024567816 卡表


欢迎各位批评指正,共同学习进步^_^😊

点赞收藏
分类:标签:
青叶竹
请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

从 Linux 内核角度探秘 JDK MappedByteBuffer

从 Linux 内核角度探秘 JDK MappedByteBuffer

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

MappedByteBuffer VS FileChannel:从内核层面对比两者的性能差异

4
3