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

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

1年前
602134

以下看法均基于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条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

记一次线上RPC超时故障排查及后续GC调优思路

记一次线上RPC超时故障排查及后续GC调优思路

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

【全网首发】从源码角度分析一次诡异的类被加载问题

【全网首发】从源码角度分析一次诡异的类被加载问题

4
3