CMS收集器的收集过程以及优化方案

1 篇文章 0 订阅

CMS收集器(老年代垃圾收集,基于标记清除算法)
过程:
初始标记阶段:
在这里插入图片描述
此阶段stop-the-world,标记存活对象,主要包括gc roots引用的对象,和新生代存活对象引用到的老年代对象。

在Java语言里,可作为GC Roots对象的包括如下几种: 1. 虚拟机栈(栈桢中的本地变量表)中的引用的对象 ; 2. 方法区中的类静态属性引用的对象 ; 3. 方法区中的常量引用的对象 ; 4. 本地方法栈中JNI的引用的对象;

此阶段可开启初始标记并行化进行优化,主要可配置参数为-XX:+CMSParallelInitialMarkEnabled

并发标记阶段:和用户线程并发运行。运行期间会发生新生代的对象晋升到老年代、或者是直接在老年代分配对象、或者更新老年代对象的引用关系等等,对于这些对象,都是需要进行重新标记的,否则有些对象就会被遗漏,发生漏标的情况。为了提高重新标记的效率,该阶段会把上述对象所在的Card标识为Dirty,后续只需扫描这些Dirty Card的对象,避免扫描整个老年代。 并发标记阶段只负责将引用发生改变的Card标记为Dirty状态,不负责处理;会产生浮动垃圾。

预清理阶段:因为并发标记阶段无法标记出全部存活对象。此阶段会扫描上一阶段标记的Dirty Card,标记Dirty Card引用的新的存活对象

可终止的预清理:这个阶段尝试着去承担下一个阶段Final Remark阶段足够多的工作。这个阶段持续的时间依赖好多的因素,由于这个阶段是重复的做相同的事情直到发生abort的条件(比如:重复的次数、多少量的工作、持续的时间等等)之一才会停止。 ps:此阶段最大持续时间为5秒,之所以可以持续5秒,另外一个原因也是为了期待这5秒内能够发生一次ygc,清理年轻带的引用,是的下个阶段的重新标记阶段,扫描年轻带指向老年代的引用的时间减少

重新标记:此阶段是stop the word .此阶段会标记整个老年代所有的存活对象。会扫描整个堆,包含_young_gen和_old_gen。对于老年代对象来说,只要他被新生代对象引用,不管这个新生代对象是否可达,那么都视之为存活对象,所以这个阶段也要扫描新生代。如果新生代对象特别多,那么扫描时间就会时间较长,引申出可能的优化点3(下文)

并发清理:和用户线程并发执行。此阶段清理那些没有标记的对象,并回收空间。因为和用户线程并发执行,所以不可避免的会产生浮动垃圾。

优化:
1,cms收集器是无法像其他收集器一样等到老年代几乎被填满再进行收集,因为在收集阶段用户线程还需要运行。所以CMS收集器一般在内存使用空间占比达到某一比例的时候CMS 收集器就会被激活。现在默认比例一般90%以上。可通过-XX:CMSInitiatingOccupancyFraction参数调整比例。
优化点主要在这个比例的设置上面,如果过小会导致CMS 收集比较频繁,影响性能;如果设置过大,剩余的内存空间无法满足程序需要就会导致concurrent mode failure 并发模式失败,临时启用Serial Old 收集器,此收集器是单线程收集器且是stop the word ,停顿时间较长,性能影响较大。实际项目中要通过分析gc 日志来设置一个合适的比例。

2,cms收集器的内存碎片的问题:标记清除算法有一个明显的缺点就是内存碎片问题:CMS收集器提供-XX:UseCMSCompactAtFullCollection开关参数,用于在cms收集器顶不住要进行full gc 的时候可爱内存碎片的整理压缩,但虽然这样内存碎片问题没有了,但停顿时间需要变长,所以这个参数一般和-XX:CMSFullGCsBeforeCompaction(用于设置执行多少次不压缩的full gc 后跟着来一次带压缩的)配合使用。

3,
新生代对象持有老年代中对象的引用,这种情况称为“跨代引用”。因它的存在,Remark阶段必须扫描整个堆来判断对象是否存活,包括新生代的不可达对象,新生代不可达对象仍然需要扫描的原因:新生代GC和老年代的GC是各自分开独立进行的,只有Minor GC时才会使用根搜索算法,标记新生代对象是否可达,也就是说虽然一些对象已经不可达,但在Minor GC发生前不会被标记为不可达,CMS也无法辨认哪些对象存活,只能全堆扫描(新生代+老年代)
加入参数-XX:+CMSScavengeBeforeRemark。
在重新标记之前,先执行一次ygc,回收掉年轻带的对象无用的对象,并将对象放入幸存带或晋升到老年代,这样再进行年轻带扫描时,只需要扫描幸存区的对象即可,一般幸存带非常小,这大大减少了扫描时间。 另外,还可以开启并行收集:-XX:+CMSParallelRemarkEnabled。

4,promotion failed 主要有 过早提升(Premature Promotion)提升失败(Promotion Failure)的问题。
过早提升原因以及解决方案:
Survivor空间太小,容纳不下全部的运行时短生命周期的对象,如果是这个原因,可以尝试将Survivor调大,否则端生命周期的对象提升过快,导致老年代很快就被占满,从而引起频繁的full gc;对象太大,Survivor和Eden没有足够大的空间来存放这些大对象。
提升失败原因以及解决方案:
提升的时候,发现老年代也没有足够的连续空间来容纳该对象。为什么是没有足够的连续空间而不是空闲空间呢?老年代容纳不下提升的对象有两种情况:老年代空闲空间不够用了; 老年代虽然空闲空间很多,但是碎片太多,没有连续的空闲空间存放该对象。优化的话需要解决内存碎片以及可优化2 结合尝试将CMS触发的阈值调低。

5,如何选择各分区大小应该依赖应用程序中对象生命周期的分布情况:如果应用存在大量的短期对象,应该选择较大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大。

参考:深入理解Java虚拟机

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值