终于把CMS垃圾收集器搞懂了~ (3)

当设置-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学习笔记

详解CMS垃圾回收机制

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwdzgx.html