当设置-XX:CMSInitiatingOccupancyFraction过大时,就可能会出现垃圾收集过程中无法分配对象的问题,导致“并发失败”(Concurrent Mode Failure),此时会临时启用Serial Old收集器来重新进行老年代收集,这会导致停顿时间更长
并发模式失败: CMS大部分阶段是与用户线程并发执行的,如果在执行垃圾收集时用户线程创建的对象直接往老年代分配,但是没有足够的内存,就会报Concurrent mode failure
晋升失败: 新生代做Minor GC的时候,老年代没有足够的空间用来存放晋升的对象,则会报Concurrent mode failure;如果由于内存碎片问题导致无法分配,就会报晋升失败
永久代空间(Java8的元空间)耗尽: 默认情况下CMS不会对永久代进行收集,一旦永久代空间耗尽,就会触发FullGC
硬件方面可以增加CPU核数, CMS是多线程垃圾收集器,默认启动线程个数为(CPU核数+3)/4,CPU核数越多,对用户线程的影响越小
停顿时间过长调优
首先需要判断是哪个阶段慢,CMS引起的停顿有:
年轻代Minor GC停顿
老年代初始、最终标记停顿
Serial Old老年代收集停顿
Full GC停顿
并发失败调优
增大老年代的空间,增加整个堆的大小
提高CMS垃圾收集频率-->>调整CMS收集的阈值
-XX:CMSInitiatingOccupancyFraction=数值 + -XX:+UseCMSInitiatingOccupancyOnly,-XX:CMSInitiatingOccupancyFraction调小,但也不能太小,太小了会导致过多无效的收集,浪费资源
《Java性能权威指南》中给出的建议: 对特定的应用程序,该标志的更优值可以根据 GC 日志中 CMS 周期首次启动失败时的值得到。具体方法是,在垃圾回收日志中寻找并发模式失效,找到后再反向查找 CMS 周期最近的启动记录,然后根据日志来计算这时候的老年代空间占用值,然后设置一个比该值更小的值。
增加CPU回收线程个数((CPU核数+3)/4)
永久代调优: 如果永久代要进行垃圾回收,就会进行Full GC,CMS默认不会处理永久代中的垃圾需要通过参数-XX:+CMSPermGenSweepingEnabled开启对方法区的收集,开启后会有专门的一组线程对永久代进行垃圾回收,同时还需要开启另一个参数-XX:+CMSClassUnloadingEnabled,使得在垃圾收集时可以卸载不用的类。
如果系统追求低延迟,那么可以选择CMS垃圾收集器,只是STW的时间缩短了,但是整个GC的时间相对更长了;
如果系统追求高吞吐,那么可以选择并行Parallel GC,虽然STW的时间长,但是可以保证非GC时间,整个系统的资源全部被应用线程占用。
周志明《深入理解Java虚拟机》
不可错过的CMS学习笔记