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垃圾回收整个过程分为六个步骤:
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=<value>(早期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>来设置,该值代表老年代堆空间的使用率。比如,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系统,可能引起主备切换,严重危害生产环境。