CMS垃圾收集器收集具体步骤
- 初始标记(Stop the world)
- 并发标记
- 预清理
- 可被终止的预清理
- 从新标记(Stop the world)
- 并发革除
- 并发重置
初始标记
标记GcRoots间接可达老年对象,新生代存活对象援用的老年代对象.整个过程在JDK1.7中是单线程的在JDK1.8中是多线程的(通过CMSParallelInitialMarkEnabled参数调整)。这个过程会导致STW。
并发标记
从初始标记
阶段标记过的对象开始,标记其它存活对象,这个阶段垃圾回收线程和利用线程同时运行。因为是同时运行,利用线程还在跑,会导致对象的降职,对象援用的变动,非凡对象间接调配到老年代。这些受到影响的老年代对象所在的Card会被标记成Dirty,用于从新标记阶段扫描,老年代对象的Card被标记为Dirty的可能起因如上面绿线所示。
预清理
因为上一个阶段是并发执行的未标记的变动对象只是标记成了Dirty对象,还没有解决,预清理
就是来标记这些Dirty对象
。如下图:在并发标记阶段3号Card被标记为Dirty。这个阶段是为从新标记
阶段做筹备。
预清理将6号标记为存活对象
可被终止的预清理
这个阶段也是为从新标记阶段做筹备,在进入从新标记阶段前,最好能进行一个Minor GC,将年老代清理一遍, 这样能够革除大部分年老代的对象(绝大部分年老代对象朝生夕死),尽量缩短从新标记
阶段进展工夫,CMS还提供了CMSScavengeBeforeRemark参数,能够在进入从新标记之前强制进行顺次Minor gc。
从新标记(remark)
预清理
和可被终止的预清理
都是为从新标记
阶段做筹备,因为从新标记
阶段会产生(STW),所以要保障尽肯能的进展时间段,不然就会影响应用程序的用户体验。这个阶段扫描的指标是:年老代+GC Roots+Dirty老年代对象,这个阶段是多线程的(XX:+CMSParallelRemarkEnabled)。
并发革除
用户线程被激活,那些未被标记的对象会被革除。'
并发重置
CMS垃圾收集器参数回到初始状态,为下一次垃圾收集做筹备。
应用CMS垃圾收集器要留神的问题
从新标记
进展工夫过长
80%的工夫花在从新标记
阶段,如果发现从新标记
阶段进展工夫过长,可尝试增加-XX:+CMSScavengeBeforeRemark
,在从新标记
之前做一次Minor GC
,目标是缩小对老年代对象的有效援用,升高从新标记
的开销。
内存碎片问题
CMS是基于标记-革除算法的,CMS只会删除垃圾对象,不会对内存空间做压缩,会造成内存碎片。咱们须要用-XX:CMSFullGCsBeforeCompaction=n
参数来调整,含意是在上一次CMS并发执行过后,还要执行多少次Full GC
才做内存压缩.
concurrent mode failure
在CMS GC过程中因为应用程序也在跑,当年老代满了,执行了Minor GC
这时候,须要将存活对象放入老年代,而此时老年代空间也有余,这时CMS还没有机会回收老年代。能够设置以下两个参数
- -XX:CMSInitiatingOccupancyFraction=75
CMS对内存的占用率达到75%将启动GC,默认为92%,太高将导致promotion failed
- -XX:+UseCMSInitiatingOccupancyOnly
如果没有设置UseCMSInitiatingOccupancyOnly
,只设置了CMSInitiatingOccupancyFraction
那么JVM只在第一次应用,后续会进行主动调整。为什么要设置以上两个参数,在垃圾收集阶段,用户线程还在运行,所以必须要留够空间让用户线程运行。CMS前五个阶段都是标记存活对象的,除了”初始标记”和”从新标记”阶段会stop the word ,其它三个阶段都是与用户线程一起跑的,就会呈现这样的状况gc线程正在标记存活对象,用户线程同时向老年代晋升新的对象,清理工作还没有开始,old gen曾经没有空间包容更多对象了,这时候就会导致concurrent mode failure, 而后就会应用串行收集器回收老年代的垃圾,导致进展的工夫十分长。
CMSInitiatingOccupancyFraction参数要设置一个正当的值,设置大了,会减少concurrent mode failure产生的频率,设置的小了,又会减少CMS频率,所以要依据利用的运行状况来选取一个正当的值。如果发现这两个参数设置大了会导致full gc,设置小了会导致频繁的CMS GC,阐明你的老年代空间过小,应该减少老年代空间的大小了。
promotion failed
在进行Minor GC时,Survivor space放不下,对象只能放入老年代,而此时老年代也放不下,大多数状况是老年代内存碎片太多,导致没有间断的空间寄存对象。
过早的降职和降职失败
产生Minor GC时,如果对象过大(Survivor Space寄存不下)基本上会放到老年代,这种景象被称为对象过早降职,这将导致老年代被中短期对象增张,肯能导致重大的性能问题。如果老年代也满了,会触发Full GC,这将会导致遍历整个堆,降职失败。
解决方案
- 如果是因为内存碎片导致的大对象晋升失败,cms须要进行空间整顿压缩;
- 如果是因为晋升过快导致的,阐明Survivor 闲暇空间有余,那么能够尝试调大 Survivor;
- 如果是因为老年代空间不够导致的,尝试将CMS触发的阈值调低。
CMS总结
- CMS只收集老年代,响应速度优先。
- 从新标记会STW,进展工夫较长,所以在这之前进行一次Minor GC,会缩小很多对老年代对象的有效援用。
- 内存碎片问题导致的Full GC,请应用
-XX:CMSFullGCsBeforeCompaction=n
,有法则对内存进行整顿缩小内存碎片。 - JDK1.7,JDK1.8设置CMS垃圾收集器
XX:+UseConcMarkSweepGC
几个重要的CMS参数
-XX:CMSFullGCsBeforeCompaction=n
Full GC
n 次后进行内存压缩整顿-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
内存占用70%将触发CMS GC
-XX:+CMSScavengeBeforeRemark
CMS GC前执行一次Minor GC