关于serial,parnew,parallel等回收器的介绍可以参考上一篇jvm垃圾回收算法以及垃圾回收器,如何选择_bjzw的博客-CSDN博客
下面具体介绍一下cms,jdk8之后就已经废弃了cms垃圾回收器,为什么要还要详解cms呢
首先,cms的回收思想需要我们了解
其次,G1回收器也复用了cms的一部分思想
一、CMS回收
CMS是在老年代的垃圾回收算法,全程为Concurrent Mark Sweep,即标记并行算法。具体有六个步骤,初始标记、并发标记、并发预处理、重新标记,并发清理、并发重置为了描述清楚,别处找了个图
1、初始标记
初始标记阶段会触发stop the world,标记所有CGRoot直接关联的对象,以及年轻代对象对老年代对象的引用,间接关联的在下一阶段。
2、并发标记
与用户线程并发执行,会遍历上一阶段标记过的对象进行遍历,并将这些对象所有的引用进行标记。第一,第二阶段就是要将老年代,新生代的里面存活的对象找出来
由于是并发执行,所以会导致有遗漏的老年代对象实例,如以下情况:年轻代部分对象移动至老年代,老年代对象引用发生变更,还有一些大对象直接进入老年代。
上面三种情况的对象所在的卡表(card table)被标记为dirty。
虽然CMS收集的是老年代的对象,但是会扫描新生代。
什么叫卡表呢?
就是将老年代的空间分成521B分解成若干张卡,卡表本身是个数组,数组中的每个元素对应一张卡,当有老年代引用新生代的对象时,对应的卡表的元素被标记为dirty(脏)如下图
并发标记就是通过卡表标记dirty,后期就不会再对老年代整个扫描了。
3、并发预清理
这个阶段是标记老年代存活的对象,尽量让重新标记阶段的STW时间短,这个阶段也会扫描老年代和新生代。
4、重新标记
这个阶段会STW,标记老年代的存活对象,这个时候扫描新生代的对象、GCRoots和之前标记为dirty的卡表老年代对象。
5、并发清理
清理未被标记的对象,释放内存空间
6、并发重置
CMS内部进行重置回收器状态,准备为下一个周期做准备。
二、如何配置CMS回收器
1、CMS相关JVM参数
-XX:+UseConcMarkSweepGC 激活CMS收集器。默认情况下使用ParNew + CMS + Serial Old的收集器组合进行内存回收,Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备收集器使用。
-XX:CMSInitiatingOccupancyFraction={x} 在老年代的空间被占用{x}%时,调用CMS算法对老年代进行垃圾回收。
-XX:CMSFullGCsBeforeCompaction={x} 在进行了{x}次CMS算法之后,对老年代进行一次compaction
-XX:+CMSPermGenSweepingEnabled & -XX:+CMSClassUnloadingEnabled 让CMS默认遍历永久代(Perm区)
-XX:ParallelCMSThreads={x} 设置CMS算法中并行线程的数量为{x}。(默认启动(CPU数量+3) / 4个线程。)
-XX:+ExplicitGCInvokesConcurrent 用户程序中可能出现利用System.gc()触发系统Full GC(将会stop-the-world),利用这个参数可以指定System.gc()直接调用CMS算法做GC。
-XX:+DisableExplicitGC 该参数直接让JVM忽略用户程序中的System.gc()
2.注意事项
-XX:+UseParNewGC, -XX:+UseConcMarkSweepGC
分别表示使用并行收集器 ParNew 对新生代进行垃圾回收,使用并发标记清除收集器 CMS 对老年代进行垃圾回收。
-XX:ParallelGCThreads, -XX:ParallelCMSThreads
分别表示 Young GC 与 CMS GC 工作时的并行线程数,建议根据处理器数量进行合理设置。
-XX:+UseCMSCompactAtFullCollection
由于 CMS GC 会产生内存碎片,且只在 Full GC 时才会进行内存碎片压缩(因此 使用 CMS 垃圾回收器避免不了 Full GC)。这个参数表示开启 Full GC 时的压缩功能,减少内存碎片。
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction
-XX:CMSInitiatingOccupancyFraction 表示触发 CMS GC 的老年代使用阈值,一般设置为 70~80(百分比),设置太小会增加 CMS GC 发现的频率,设置太大可能会导致并发模式失败或晋升失败。
-XX:+UseCMSInitiatingOccupancyOnly 表示 CMS GC 只基于 CMSInitiatingOccupancyFraction 触发,如果未设置该参数则 JVM 只会根据 CMSInitiatingOccupancyFraction 触发第一次 CMS GC ,后续还是会自动触发。建议同时设置这两个参数。
-XX:+CMSClassUnloadingEnabled 表示开启 CMS 对永久代的垃圾回收(或元空间),避免由于永久代空间耗尽带来 Full GC。
-XX:+CMSParallelRemarkEnabled 开启降低标记停顿。
A、提升失败(promotion failed)
在 Minor GC 过程中,Survivor Unused 可能不足以容纳 Eden 和另一个 Survivor 中的存活对象, 那么多余的存货对象将被移到老年代, 称为过早提升(Premature Promotion)。 这会导致老年代中短期存活对象的增长, 可能会引发严重的性能问题。
再进一步,如果老年代没有足够的空间放下这些存活对象,或者老年代有足够的空间,但是由于碎片较多,这时如果新生代要转移到老年带的对象比较大,所以,必须尽可能提早触发老年代的CMS回收来避免这个问题(promotion failed时老年代CMS还没有机会进行回收,又放不下转移到老年带的对象,因此会出现下一个问题concurrent mode failure,需要stop-the-wold GC- Serail Old)。
解决办法:
针对一些大的朝生熄灭的对象,Surivor空间存放不下,就会存放到老年代,针对这种情况,可以扩大Survivor空间。
老年代空间碎片,无法放下大对象,就在进行一定次数的Full GC(标记清除)的时候进行一次标记整理算法。
B、并发模式失败(concurrent mode failure)
由于cms的无法处理浮动垃圾(Floating Garbage)引起的。这个跟cms的机制有关。cms的并发清理阶段,用户线程还在运行,因此不断有新的垃圾产生,而这些垃圾不在这次清理标记的范畴里头,cms无法在本次gc清除掉,这些就是浮动垃圾。
由于这种机制,cms年老代回收的阈值不能太高,否则就容易预留的内存空间很可能不够(因为本次gc同时还有浮动垃圾产生),从而导致concurrent mode failure发生。可以通过-XX:CMSInitiatingOccupancyFraction的值来调优。