乐趣区

关于jvm:CMS垃圾收集器

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

退出移动版