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

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

1年前
574534

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

为你推荐

不起眼,但是足以让你有收获的JVM内存分析案例
分析 这个问题说白了,就是说有些int[]对象不知道是哪里来的,于是我拿他的例子跑了跑,好像还真有这么回事。点该 dump 文件详情,查看相关的 int[] 数组,点该对象的“被引用对象”,发现所
从一起GC血案谈到反射原理
前言 首先回答一下提问者的问题。这主要是由于存在大量反射而产生的临时类加载器和 ASM 临时生成的类,这些类会被保留在 Metaspace,一旦 Metaspace 即将满的时候,就会触发 Fu
关于内存溢出,咱再聊点有意思的?
概述 上篇文章讲了JVM在GC上的一个设计缺陷,揪出一个导致GC慢慢变长的JVM设计缺陷,可能有不少人还是没怎么看明白的,今天准备讲的大家应该都很容易看明白 本文其实很犹豫写不写,因为感觉没有
协助美团kafka团队定位到的一个JVM Crash问题
概述 有挺长一段时间没写技术文章了,正好这两天美团kafka团队有位小伙伴加了我微信,然后咨询了一个JVM crash的问题,大家对crash的问题都比较无奈,因为没有现场,信息量不多,碰到这类问题我
又发现一个导致JVM物理内存消耗大的Bug(已提交Patch)
概述 最近我们公司在帮一个客户查一个JVM的问题(JDK1.8.0_191-b12),发现一个系统老是被OS Kill掉,是内存泄露导致的。在查的过程中,阴差阳错地发现了JVM另外的一个Bug。这个B
JVM实战:优化我的IDEA GC
IDEA是个好东西,可以说是地球上最好的Java开发工具,但是偶尔也会卡顿,仔细想想IDEA也是Java开发的,会不会和GC有关,于是就有了接下来对IDEA的GC进行调优 IDEA默认JVM参数: -
不起眼,但是足以让你收获的JVM内存案例
今天的这个案例我觉得应该会让你涨姿势吧,不管你对JVM有多熟悉,看到这篇文章,应该还是会有点小惊讶的,不过我觉得这个案例我分享出来,是想表达不管多么奇怪的现象请一定要追究下去,会让你慢慢变得强大起来,
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得