CMS(Concurrent Mark Sweep)收集器

CMS(Concurrent Mark Sweep)收集器

C :  Concurrent

M :  标记(marking)对象 :GC必须记住哪些对象可达,以便删除不可达的对象 

S :  清除(sweeping) : 删除未标记的对象并释放它们的内存


CMS是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高,但它尽可能降低GC时服务的停顿时间。

使用标记-清除算法(Mark Sweep),在运行时会产生内存碎片

虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩。

-XX:+UseConcMarkSweepGC

激活CMS收集器。默认HotSpot JVM使用的是并行收集器

-XX:+UseCMSCompactAtFullCollection  

设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效
-XX:+CMSFullGCBeforeCompaction
设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程
通常与UseCMSCompactAtFullCollection参数一起使用

CMS垃圾回收整个过程分为六个步骤:

1. 初始标记 (CMS initial mark)  会STW(Stop The World)

为了收集应用程序的对象引用需要暂停应用程序线程

该阶段完成后,应用程序线程再次启动


2. 并发标记 (CMS concurrent mark)

从第一阶段收集到的对象引用开始,遍历所有其他的对象引用


3. 并发预清理(CMS-concurrent-preclean)

改变当运行第二阶段时,由应用程序线程产生的对象引用,以更新第二阶段的结果


4. 重新标记 (CMS remark)  会STW

由于第三阶段是并发的,对象引用可能会发生进一步改变,因此应用程序线程会再一次被暂停以更新这些变化

并且在进行实际的清理之前确保一个正确的对象引用视图


5. 并发清理 (CMS concurrent sweep)  

所有不再被引用的对象将从堆里清除掉


6. 并发重置:

收集器做一些收尾的工作,以便下一次GC周期能有一个干净的状态

从上面的6个阶段可以看出,cms为老生代垃圾回收提供了几乎完全并发的解决方案,然而新生代则仍然通过“stop-the-world”方法来收集,对于交互应用,暂停时间也是可以接受的,因为新生代的垃圾回收时间是很短暂的。


CMS的两个挑战:

1. 堆碎片

CMS收集器默认并没有任何碎片整理的机制。所以可能会出现这样的情形:

即使总的堆大小远没有耗尽但却不能分配对象,仅仅是因为没有足够连续的空间完全容纳对象

当这种事发生后,JVM会触发Full GC,

full gc会采用吞吐量的垃圾回收算法(1,引用计数,2,标记-清理3,复制4,标记-整理)中的复制和标记清理来解决碎片问题,但是又会暂停时间,因此尽管cms可以带来几乎完全并发性,但是仍可能会出现长时间的“stop-the-world”的风险,因此这种设计,不能避免的----我们只能通过调优收集器来尽可能的避免,要想100%的解决“stop-the-world”,是不现实。

 

2. 对象分配率高

获取对象实例的频率高于收集器清除堆里死对象的频率

并发模式失败: 老年代没有足够的可用空间来容纳一个从年轻代提升过来的对象

此时JVM会执行堆碎片整理:触发Full GC

 

当这些情形之一出现的时候,经常被证实是老年代有大量不必要的对象。

一个可行的办法: 增加年轻代的堆大小(增加Survior区的个数),以防止年轻代生命周期短的对象提前进入老年代。

其他方法:另一个办法就似乎利用分析器,快照运行系统的堆转储

                  利用jmap和jhat分析过度的对象分配,找出这些对象,最终减少这些对象的申请。


下面我看看大多数与CMS收集器调优相关的JVM标志参数。

-XX:+UseConcMarkSweepGC

该标志首先是激活CMS收集器。默认HotSpot JVM使用的是并行收集器。

-XX:UseParNewGC

当使用CMS收集器时,该标志激活年轻代使用多线程并行执行垃圾回收。这令人很惊讶,我们不能简单在并行收集器中重用-XX:UserParNewGC标志,因为概念上年轻代用的算法是一样的。然而,对于CMS收集器,年轻代GC算法和老年代GC算法是不同的,因此年轻代GC有两种不同的实现,并且是两个不同的标志。

注意最新的JVM版本,当使用-XX:+UseConcMarkSweepGC时,-XX:UseParNewGC会自动开启。因此,如果年轻代的并行GC不想开启,可以通过设置-XX:-UseParNewGC来关掉。

-XX:+CMSConcurrentMTEnabled

当该标志被启用时,并发的CMS阶段将以多线程执行(因此,多个GC线程会与所有的应用程序线程并行工作)。该标志已经默认开启,如果顺序执行更好,这取决于所使用的硬件,多线程执行可以通过-XX:-CMSConcurremntMTEnabled禁用。

 -XX:ConcGCThreads

标志-XX:ConcGCThreads=(早期JVM版本也叫-XX:ParallelCMSThreads)定义并发CMS过程运行时的线程数。比如value=4意味着CMS周期的所有阶段都以4个线程来执行。尽管更多的线程会加快并发CMS过程,但其也会带来额外的同步开销。因此,对于特定的应用程序,应该通过测试来判断增加CMS线程数是否真的能够带来性能的提升。

如果还标志未设置,JVM会根据并行收集器中的-XX:ParallelGCThreads参数的值来计算出默认的并行CMS线程数。该公式是ConcGCThreads = (ParallelGCThreads + 3)/4。因此,对于CMS收集器, -XX:ParallelGCThreads标志不仅影响“stop-the-world”垃圾收集阶段,还影响并发阶段。

总之,有不少方法可以配置CMS收集器的多线程执行。正是由于这个原因,建议第一次运行CMS收集器时使用其默认设置, 然后如果需要调优再进行测试。只有在生产系统中测量(或类生产测试系统)发现应用程序的暂停时间的目标没有达到 , 就可以通过这些标志应该进行GC调优。

-XX:CMSInitiatingOccupancyFraction

当堆满之后,并行收集器便开始进行垃圾收集,例如,当没有足够的空间来容纳新分配或提升的对象。对于CMS收集器,长时间等待是不可取的,因为在并发垃圾收集期间应用持续在运行(并且分配对象)。因此,为了在应用程序使用完内存之前完成垃圾收集周期,CMS收集器要比并行收集器更先启动。

因为不同的应用会有不同对象分配模式,JVM会收集实际的对象分配(和释放)的运行时数据,并且分析这些数据,来决定什么时候启动一次CMS垃圾收集周期。为了引导这一过程, JVM会在一开始执行CMS周期前作一些线索查找。该线索由 -XX:CMSInitiatingOccupancyFraction=来设置,该值代表老年代堆空间的使用率。比如,value=75意味着第一次CMS垃圾收集会在老年代被占用75%时被触发。通常CMSInitiatingOccupancyFraction的默认值为68(之前很长时间的经历来决定的)。

-XX:+UseCMSInitiatingOccupancyOnly

我们用-XX+UseCMSInitiatingOccupancyOnly标志来命令JVM不基于运行时收集的数据来启动CMS垃圾收集周期。而是,当该标志被开启时,JVM通过CMSInitiatingOccupancyFraction的值进行每一次CMS收集,而不仅仅是第一次。然而,请记住大多数情况下,JVM比我们自己能作出更好的垃圾收集决策。因此,只有当我们充足的理由(比如测试)并且对应用程序产生的对象的生命周期有深刻的认知时,才应该使用该标志。

-XX:+CMSClassUnloadingEnabled

相对于并行收集器,CMS收集器默认不会对永久代进行垃圾回收。如果希望对永久代进行垃圾回收,可用设置标志-XX:+CMSClassUnloadingEnabled。在早期JVM版本中,要求设置额外的标志-XX:+CMSPermGenSweepingEnabled。注意,即使没有设置这个标志,一旦永久代耗尽空间也会尝试进行垃圾回收,但是收集不会是并行的,而再一次进行Full GC。

-XX:+CMSIncrementalMode

该标志将开启CMS收集器的增量模式。增量模式经常暂停CMS过程,以便对应用程序线程作出完全的让步。因此,收集器将花更长的时间完成整个收集周期。因此,只有通过测试后发现正常CMS周期对应用程序线程干扰太大时,才应该使用增量模式。由于现代服务器有足够的处理器来适应并发的垃圾收集,所以这种情况发生得很少。

-XX:+ExplicitGCInvokesConcurrent and -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

如今,被广泛接受的最佳实践是避免显式地调用GC(所谓的“系统GC”),即在应用程序中调用system.gc()。然而,这个建议是不管使用的GC算法的,值得一提的是,当使用CMS收集器时,系统GC将是一件很不幸的事,因为它默认会触发一次Full GC。幸运的是,有一种方式可以改变默认设置。标志-XX:+ExplicitGCInvokesConcurrent命令JVM无论什么时候调用系统GC,都执行CMS GC,而不是Full GC。第二个标志-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses保证当有系统GC调用时,永久代也被包括进CMS垃圾回收的范围内。因此,通过使用这些标志,我们可以防止出现意料之外的”stop-the-world”的系统GC。

-XX:+DisableExplicitGC

然而在这个问题上…这是一个很好提到- XX:+ DisableExplicitGC标志的机会,该标志将告诉JVM完全忽略系统的GC调用(不管使用的收集器是什么类型)。对于我而言,该标志属于默认的标志集合中,可以安全地定义在每个JVM上运行,而不需要进一步思考。


名词解释:

"stop-the-world" 机制简称STW,即,在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集帮助器线程之外的线程都被挂起


  • Java中一种全局暂停的现象
  • 全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互
  • 多半由于GC引起
  • Dump线程
  • 死锁检查
  • 堆Dump
  • GC时为什么会有全局停顿?
–类比在聚会时打扫房间,聚会时很乱,又有新的垃圾产生,房间永远打扫不干净,只有让大家停止活动了,才能将房间打扫干净。

  • 危害
  • 长时间服务停止,没有响应;
  • 遇到HA系统,可能引起主备切换,严重危害生产环境。

你可能感兴趣的:(jvm调优)