前文已经讲过,CMS
是老年代垃圾收集器,在收集过程中可以与用户线程并发操作。它可以与Serial
收集器和Parallel New
收集器搭配使用。CMS
牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。
参数 | 类型 | 默认值 | 作用 |
---|---|---|---|
-XX:+UseConcMarkSweepGC | boolean | false | 老年代采用CMS收集器收集 |
–XX:ParallelGCThreads=n | int | (ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8) | 老年代采用CMS收集器收集 |
-XX:CMSInitiatingOccupancyFraction | int | 92 | 年代堆空间的使用率。比如value=75意味着第一次CMS垃圾收集会在老年代被占用75%时被触发。 |
-XX:+UseCMSInitiatingOccupancyOnly | boolean | false | 只用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整 |
由后台线程ConcurrentMarkSweepThread
循环判断(默认2s)是否需要触发。
如果没有设置-XX:+UseCMSInitiatingOccupancyOnly
,虚拟机会根据收集的数据决定是否触发 老年代使用率达到阈值 CMSInitiatingOccupancyFraction
,默认92%。永久代的使用率达到阈值 CMSInitiatingPermOccupancyFraction
,默认92%,前提是开启 CMSClassUnloadingEnabled
。新生代的晋升担保失败。
YGC
过程发生Promotion Failed
,进而对老年代进行回收 比如执行了System.gc()
,前提是没有参数ExplicitGCInvokesConcurrent
这是CMS
中两次stop-the-world
事件中的一次。这一步的作用是标记存活的对象,有两部分:
标记老年代中所有的GC Roots
对象 标记年轻代中活着的对象引用到的老年代的对象
CMS-initial-mark:961330K(1572864K)
指标记时老年代的已用空间和总空间
该阶段GC
线程和应用线程并发执行,遍历InitialMarking
阶段标记出来的存活对象,然后继续递归标记这些对象可达的对象。
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 2.787/3.329 secs] [Times: user=12.12 sys=0.64, real=3.33 secs]
第一行CMS-concurrent-mark-start
标识标记阶段开始。第二行中的“2.787/3.329 secs”表示标记阶段的耗时。表示花费了2.787cpu时间,3.329系统时间。
由于在并发标记阶段,应用线程和GC
线程是并发执行的,因此可能产生新的对象或对象关系发生变化,例如:
新生代的对象晋升到老年代;直接在老年代分配对象;老年代对象的引用关系发生变更;
http://static.cyblogs.com/20190702174034511.png
该阶段会把上述对象所在的Card
标识为Dirty
,后续只需扫描这些Dirty Card
的对象,避免扫描整个老年代。标记dirty card
能够到达的对象
http://static.cyblogs.com/20190702180248905.png
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.342/0.477 secs] [Times: user=1.79 sys=0.10, real=0.48 secs]
该阶段发生的前提是,新生代Eden
区的内存使用量大于参数CMSScheduleRemarkEdenSizeThreshold
默认是2M,如果新生代的对象太少,就没有必要执行该阶段,直接执行重新标记阶段。在该阶段,主要循环的做两件事:
处理 From
和 To
区的对象,标记可达的老年代对象 和上一个阶段一样,扫描处理Dirty Card
中的对象
暂停所有用户线程,重新扫描堆中的对象,进行可达性分析,标记活着的对象。有了前面的基础,这个阶段的工作量被大大减轻,停顿时间因此也会减少。注意这个阶段是多线程的。
遍历新生代对象,重新标记 根据GC Roots
,重新标记 遍历老年代的Dirty Card
,重新标记,这里的Dirty Card
大部分已经在clean
阶段处理过
通过以上5个阶段的标记,老年代所有存活的对象已经被标记并且现在要通过Garbage Collector
采用清扫的方式回收那些不能用的对象了。这个阶段主要是清除那些没有标记的对象并且回收空间;由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。
一般CMS
的GC
耗时80%都在remark阶段,如果发现remark
阶段停顿时间很长,可以尝试添加该参数:-XX:+CMSScavengeBeforeRemark
。在执行remark
操作之前先做一次Young GC
,目的在于减少年轻代对老年代的无效引用,降低remark
时的开销。
CMS
是基于标记-清除算法的,CMS
只会删除无用对象,不会对内存做压缩,会造成内存碎片,这时候我们需要用到这个参数:-XX:CMSFullGCsBeforeCompaction=n
意思是说在上一次CMS
并发GC
执行过后,到底还要再执行多少次full GC
才会做压缩。默认是0
这个异常发生在cms正在回收的时候。执行CMS GC
的过程中,同时业务线程也在运行,当年轻带空间满了,执行YGC
时,需要将存活的对象放入到老年代,而此时老年代空间不足,这时CMS
还没有机会回收老年带产生的,或者在做Minor GC
的时候,新生代救助空间放不下,需要放入老年代,而老年代也放不下而产生的。
Minor GC
过程中,Survivor Unused
可能不足以容纳 Eden
和另一个 Survivor
中的存活对象, 那么多余的将被移到老年代, 称为过早提升(Premature Promotion
),这会导致老年代中短期存活对象的增长, 可能会引发严重的性能问题。再进一步,如果老年代满了, Minor GC
后会进行 Full GC
, 这将导致遍历整个堆, 称为提升失败(Promotion Failure
)。Survivor
空间太小,容纳不下全部的运行时短生命周期的对象,如果是这个原因,可以尝试将Survivor
调大,否则端生命周期的对象提升过快,导致老年代很快就被占满,从而引起频繁的full gc
;对象太大,Survivor
和Eden
没有足够大的空间来存放这些大对象。REF:https://juejin.im/post/5c39920b6fb9a049e82bbf94
如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。