CMS垃圾收集器总结
一 公司CMS参数
先说一下公司CMS参数,年轻代3.5G, 其中survivor 50M。老年代1.5G,其实用不到500M,原空间250M。
二 常见参数配置
1 开启 CMS
首先,要说的是,CMS只是老年代的垃圾收集器。其年轻代使用的是ParNew垃圾收集器。
其次,JDK8默认的垃圾收集器并不是CMS,需要手动指定。-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
2 设置堆大小
使用CMS建议总堆空间不超过8G,最好是6G以内。因为如果发生FGC,太大的堆空间会让FGC的STW时间变的特别的长。
-Xmx4G -Xms4G -Xmn1512M
3 线程栈
JDK8默认的线程栈大小是1M,绝大多数微服务项目可以调整为512K -XSS512K
4 -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly
表示只有在老年代达到了70%才进行回收
5 -XX:MetaspaceSize=314572800 其实是300M
因为之前发生过元空间引发的FGC,所以我们项目把这个参数调大了。因为本项目的特点是,会请求大量的接口,引入大量的类。
6 dump路径
必须配置,因为如果发生了OOM,不dump的话,没法定位问题。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/applogs/system/error.dump
7 GC 日志
必须配置
-XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-Xloggc:/data/log/gclog/gc.log
8 压缩
CMS是一种并发标记清除算法,而不是标记整理算法。所以,它不会进行碎片内存的移动和整理。
-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0
0表示每次发生fullgc 都进行压缩整理
9 卸载类
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
如果项目中使用了Netty,或者代码里用了堆外内存,只有FGC才能回收这样的堆外内存。也就是说想手动清理堆外内存就得执行System.gc()。而System.gc()触发的是serial old垃圾收集器,时间会很长,一般在5s左右。而FGC的STW的时间会很长,(CMS的完整FGC是退化为线性GC)。而CMS GC暂停服务的时间较短。因此,加了这个参数可以有效地回收堆外内存并且减少暂停时间。
这两个参数也是用来改变System.gc()的默认行为用的;不同的 是这两个参数只能配合CMS使用(-XX:+UseConcMarkSweepGC),而且System.gc()还是会触发GC的,只不过不是触发一个 完全stop-the-world的full GC,而是一次并发GC周期。也还是会触发对外内存的回收。
- CMS采用了Mark-Sweep算法,最后会产生许多内存碎片,当到一定数量时,CMS无法清理这些碎片了,CMS会让Serial Old垃圾处理器来清理这些垃圾碎片,而Serial Old垃圾处理器是单线程操作进行清理垃圾的,效率很低。所以使用CMS就会出现一种情况,硬件升级了,却越来越卡顿,其原因就是因为进行Serial Old GC时,效率过低。
- 解决方案:使用Mark-Sweep-Compact算法,减少垃圾碎片
- 调优参数(配套使用):-XX:+UseCMSCompactAtFullCollection 开启CMS的压缩
-XX:CMSFullGCsBeforeCompaction 默认为0,指经过多少次CMS FullGC才进行压缩 - 当JVM认为内存不够,再使用CMS进行并发清理内存可能会发生OOM的问题,而不得不进行Serial Old GC,Serial Old是单线程垃圾回收,效率低
- 解决方案:降低触发CMS GC的阈值,让浮动垃圾不那么容易占满老年代
- 调优参数:-XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让老年代占用率达到该值就进行CMS GC
附上公司启动脚本
java -jar -javaagent:/data/gravity/gravity-agent.jar=appName=trade-om-app,baseUrl=http://gravity-api.amh-group.com,appType=jar
-Xms8192m -Xmx8192m -Xmn5460m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=600m -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/applogs/system/error.dump -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
-XX:CMSInitiatingOccupancyFraction=70 -XX:+PrintGCDateStamps -XX:+UseCMSInitiatingOccupancyOnly -XX:+UnlockDiagnosticVMOptions
-XX:+UnsyncloadClass -XX:+UseParNewGC -XX:+PrintGCDetails -Xloggc:/data/applogs/system/gc_202007061528.log
可以看到 配置eden和survivor比例没有配置,应该是使用默认的。
IBM论文里说据他们统计95%的对象朝生夕死一样存活时间极短,为了保险默认实际使用了90%:设置eden 和survivor(两个)为4:1,每次GC都将Eden和其中一个survivor(from)中的存活对象复制到剩余的survivor(to)中