首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CMS需要注意的问题

CMS需要注意的问题

作者头像
黑洞代码
发布2021-04-23 15:01:06
6880
发布2021-04-23 15:01:06
举报

CMS相关参数

CMS不是full GC

有一点需要注意的是:CMS并发GC不是“full GC”。HotSpot VM里对concurrent collection和full collection有明确的区分。所有带有“FullCollection”字样的VM参数都是跟真正的full GC相关,而跟CMS并发GC无关的,cms收集算法只是清理老年代。

减少remark阶段停顿

一般CMS的GC耗时 80%都在remark阶段,如果发现remark阶段停顿时间很长,可以尝试添加该参数:

-XX:+CMSScavengeBeforeRemark

在执行remark操作之前先做一次ygc,目的在于减少ygen对oldgen的无效引用,降低remark时的开销,如果添加该参数后 ”ygc停顿时间+remark时间<添加该参数之前的remark时间“,说明该参数是有效的;

内存碎片

CMS是基于标记-清除算法的,只会将标记为未存活的对象删除,并不会移动对象整理内存空间,会造成内存碎片,这时候我们需要用到这个参数;

-XX:CMSFullGCsBeforeCompaction=n

这个参数大部分人的使用方式都是错误的,往往会导致设置后问题更大。

CMS GC要决定是否在full GC时做压缩,会依赖几个条件。其中,

1.UseCMSCompactAtFullCollection 与 CMSFullGCsBeforeCompaction 是搭配使用的;前者目前默认就是true了,也就是关键在后者上。2.用户调用了System.gc(),而且DisableExplicitGC没有开启。3.young gen报告接下来如果做增量收集会失败;简单来说也就是young gen预计old gen没有足够空间来容纳下次young GC晋升的对象。上述三种条件的任意一种成立都会让CMS决定这次做full GC时要做压缩。

CMSFullGCsBeforeCompaction 说的是,在上一次CMS并发GC执行过后,到底还要再执行多少次full GC才会做压缩。默认是0,也就是在默认配置下每次CMS GC顶不住了而要转入full GC的时候都会做压缩。如果把CMSFullGCsBeforeCompaction配置为10,就会让上面说的第一个条件变成每隔10次真正的full GC才做一次压缩(而不是每10次CMS并发GC就做一次压缩,目前VM里没有这样的参数)。这会使full GC更少做压缩,也就更容易使CMS的old gen受碎片化问题的困扰。本来这个参数就是用来配置降低full GC压缩的频率,以期减少某些full GC的暂停时间。CMS回退到full GC时用的算法是mark-sweep-compact,但compaction是可选的,不做的话碎片化会严重些但这次full GC的暂停时间会短些;这是个取舍。

concurrent mode failure

这个异常发生在cms正在回收的时候。执行CMS GC的过程中,同时业务线程也在运行,当年轻带空间满了,执行ygc时,需要将存活的对象放入到老年代,而此时老年代空间不足,这时CMS还没有机会回收老年带产生的,或者在做Minor GC的时候,新生代救助空间放不下,需要放入老年代,而老年代也放不下而产生的。设置cms触发时机有两个参数:

-XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70

-XX:CMSInitiatingOccupancyFraction=70 是指设定CMS在对内存占用率达到70%的时候开始GC。

-XX:+UseCMSInitiatingOccupancyOnly如果不指定, 只是用设定的回收阈值CMSInitiatingOccupancyFraction,则JVM仅在第一次使用设定值,后续则自动调整会导致上面的那个参数不起作用。

为什么要有这两个参数?

由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。

CMS前五个阶段都是标记存活对象的,除了”初始标记”和”重新标记”阶段会stop the word ,其它三个阶段都是与用户线程一起跑的,就会出现这样的情况gc线程正在标记存活对象,用户线程同时向老年代提升新的对象,清理工作还没有开始,old gen已经没有空间容纳更多对象了,这时候就会导致concurrent mode failure, 然后就会使用串行收集器回收老年代的垃圾,导致停顿的时间非常长。

CMSInitiatingOccupancyFraction参数要设置一个合理的值,设置大了,会增加concurrent mode failure发生的频率,设置的小了,又会增加CMS频率,所以要根据应用的运行情况来选取一个合理的值。

如果发现这两个参数设置大了会导致fullgc,设置小了会导致频繁的cmsgc,说明你的老年代空间过小,应该增加老年代空间的大小了;

promotion failed

这个异常发生在年轻带回收的时候;在进行Minor GC时,Survivor Space放不下,对象只能放入老年代,而此时老年代也放不下造成的,多数是由于老年带有足够的空闲空间,但是由于碎片较多,新生代要转移到老年带的对象比较大,找不到一段连续区域存放这个对象导致的,以下是一段promotion failed的日志:106.641: [GC 106.641: [ParNew (promotion failed): 14784K->14784K(14784K), 0.0370328 secs]106.678: [CMS106.715: [CMS-concurrent-mark: 0.065/0.103 secs] [Times: user=0.17 sys=0.00, real=0.11 secs] (concurrent mode failure): 41568K->27787K(49152K), 0.2128504 secs] 52402K->27787K(63936K), [CMS Perm : 2086K->2086K(12288K)], 0.2499776 secs] [Times: user=0.28 sys=0.00, real=0.25 secs]

过早提升与提升失败 在 Minor GC 过程中,Survivor Unused 可能不足以容纳 Eden 和另一个 Survivor 中的存活对象, 那么多余的将被移到老年代, 称为过早提升(Premature Promotion),这会导致老年代中短期存活对象的增长, 可能会引发严重的性能问题。再进一步, 如果老年代满了, Minor GC 后会进行 Full GC, 这将导致遍历整个堆, 称为提升失败(Promotion Failure)。

早提升的原因

1.Survivor空间太小,容纳不下全部的运行时短生命周期的对象,如果是这个原因,可以尝试将Survivor调大,否则短生命周期的对象提升过快,导致老年代很快就被占满,从而引起频繁的full gc;2.对象太大,Survivor和Eden没有足够大的空间来存放这些大象;

提升失败原因 当提升的时候,发现老年代也没有足够的连续空间来容纳该对象。为什么是没有足够的连续空间而不是空闲空间呢?老年代容纳不下提升的对象有两种情况:

1.老年代空闲空间不够用了;2.老年代虽然空闲空间很多,但是碎片太多,没有连续的空闲空间存放该对象;

解决方法

1.如果是因为内存碎片导致的大对象提升失败,cms需要进行空间整理压缩;2.如果是因为提升过快导致的,说明Survivor 空闲空间不足,那么可以尝试调大 Survivor;3.如果是因为老年代空间不够导致的,尝试将CMS触发的阈值调低;

其它导致回收停顿时间变长原因

1.linux使用了swap,内存换入换出(vmstat),尤其是开启了大内存页的时候,因为swap只支持4k的内存页,大内存页的大小为2M,大内存页在swap的交换的时候需要先将swap中4k内存页合并成一个大内存页再放入内存或将大内存页切分为4k的内存页放入swap,合并和切分的操作会导致操作系统占用cup飙高,用户cpu占用反而很低;2.除了swap交换外,网络io(netstat)、磁盘I/O (iostat)在 GC 过程中发生会使 GC 时间变长。

如果是以上原因,就要去查看gc日志中的Times耗时:

[Times: user=0.00 sys=0.00, real=0.00 secs]

user是用户线程占用的时间,sys是系统线程占用的时间,如果是io导致的问题,会有两种情况

1.user与sys时间都非常小,但是real却很长,如下:

[ Times: user=0.51 sys=0.10, real=5.00 secs ]

user+sys的时间远远小于real的值,这种情况说明停顿的时间并不是消耗在cup执行上了,不是cup肯定就是io导致的了,所以这时候要去检查系统的io情况。

1.sys时间很长,user时间很短,real几乎等于sys的时间,如下:

[ Times: user=0.11 sys=31.10, real=33.12 secs ]

这时候其中一种原因是开启了大内存页,还开启了swap,大内存进行swap交换时会有这种现象;

增加线程数

CMS默认启动的回收线程数目是 (ParallelGCThreads + 3)/4) ,这里的ParallelGCThreads是年轻代的并行收集线程数,感觉有点怪怪的;年轻代的并行收集线程数默认是(ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8),可以通过-XX:ParallelGCThreads= N 来调整;如果要直接设定CMS回收线程数,可以通过-XX:ParallelCMSThreads=n,注意这个n不能超过cpu线程数,需要注意的是增加gc线程数,就会和应用争抢资源;

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 落叶飞翔的蜗牛 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CMS相关参数
  • CMS不是full GC
  • 减少remark阶段停顿
  • 内存碎片
  • concurrent mode failure
  • promotion failed
  • 其它导致回收停顿时间变长原因
  • 增加线程数
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档