十种GC收集器及原理(4)—CMS

6 篇文章 1 订阅
4 篇文章 0 订阅

该垃圾收集器的正式名称是“大多数同时标记并清除垃圾收集器”。它在Old Gen中使用并发标记清除算法。

该收集器旨在避免在老年代中进行收集时出现长时间停顿。它通过两种方法来实现。首先,它不会压缩回收空间,而是使用空闲列表来管理回收空间。其次,它在标记和清除阶段与应用程序同时完成大部分工作。这意味着垃圾回收不会显式停止应用程序线程执行这些阶段。但是,应该注意的是,它仍然与应用程序线程竞争CPU时间。默认情况下,此GC算法使用的线程数等于计算机物理内核数的1/4。

可以通过在命令行上指定以下选项来选择此垃圾收集器:

java -XX:+UseConcMarkSweepGC com.mypackages.MyExecutableClass

如果您的主要目标是减少延迟,则此组合是多核计算机上的不错选择。减少单个GC暂停的持续时间将直接影响最终用户对您的应用程序的感知方式,从而使他们感到应用程序响应速度更快。由于大多数情况下,GC至少会占用一些CPU资源,并且不执行应用程序的代码,因此与CPU绑定的应用程序相比,CMS的吞吐量通常比并行GC差。

现在让我们通过查看一个Minor GC暂停和一个Major GC暂停的GC日志来了解该算法在实践中的应用方式:

2015-05-26T16:23:07.219-0200: 64.322: [GC (Allocation Failure) 64.322: [ParNew: 613404K->68068K(613440K), 0.1020465 secs] 10885349K->10880154K(12514816K), 0.1021309 secs] [Times: user=0.78 sys=0.01, real=0.11 secs]

2015-05-26T16:23:07.321-0200: 64.425: [GC (CMS Initial Mark) [ CMS-initial-mark: 10812086K(11901376K)] 10887844K(12514816K), 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

2015-05-26T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]

2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-mark: 0.035/0.035 secs] [Times: user=0.07 sys=0.00, real=0.03 secs]

2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]

2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]

2015-05-26T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean: 0.167/1.074 secs] [Times: user=0.20 sys=0.00, real=1.07 secs]

2015-05-26T16:23:08.447-0200: 65.550: [GC (CMS Final Remark) [YG occupancy: 387920 K (613440 K)]65.550: [Rescan (parallel) , 0.0085125 secs]65.559: [weak refs processing, 0.0000243 secs]65.559: [class unloading, 0.0013120 secs]65.560: [scrub symbol table, 0.0008345 secs]65.561: [scrub string table, 0.0001759 secs][1 CMS-remark: 10812086K(11901376K)] 11200006K(12514816K), 0.0110730 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]

2015-05-26T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]

2015-05-26T16:23:08.485-0200: 65.588: [CMS-concurrent-sweep: 0.027/0.027 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]

2015-05-26T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]

2015-05-26T16:23:08.497-0200: 65.601: [CMS-concurrent-reset: 0.012/0.012 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

    Minor GC

日志中的第一个GC事件表示Major GC正在清理Young空间。 让我们分析一下此收集器组合在这方面的行为:

  1. [ 2015-05-26T16:23:07.219-0200 ] – GC事件开始的时间。
  2. [ 64.322 ] – GC事件开始的时间,相对于JVM启动时间。以秒为单位。
  3. [ GC ] –标记以区分Minor GC和Full GC。这次表明这是Minor GC。
  4. [ Allocation Failure ]–收集的原因。在这种情况下,由于请求的分配不适合Young Gen中的任何区域而触发了GC。
  5. [ ParNew ] –使用的收集器的名称,这一次表示在Young Generation中使用的并行“标记-复制”垃圾收集器,该垃圾收集器旨在与Old Generation中的CMS垃圾收集器一起使用。
  6. [ 613404K-> 68068K ] –收集前后新生代的堆空间使用情况。
  7. [ (613440K) ]–新生代的堆空间总数。
  8. [ 0.1020465 secs ]–不进行最终清理的收集持续时间。
  9. [ 10885349K-> 10880154K ] –收集之前和之后使用的总堆。
  10. [(12514816K)]–可用堆总数。
  11. [ 0.1021309 secs ]–垃圾收集器在Young Generation中标记和复制活动对象所花费的时间。这包括与CMS收集器的通信开销,升级对老年代足够老的对象以及在垃圾收集周期结束时进行一些最终清理。
  12. [ Times: user=0.78 sys=0.01, real=0.11 secs ] – GC事件的持续时间,以不同类别衡量:
  • User–此收集期间垃圾收集器线程消耗的总CPU时间
  • sys –在OS调用或等待系统事件上花费的时间
  • real–应用程序停止的时钟时间。使用并行GC时,该数字应接近(用户时间+系统时间)除以垃圾收集器使用的线程数。在这种特殊情况下,使用了8个线程。请注意,由于某些活动不可并行化,因此它总是超出比率一定数量。

由此可见,在收集之前,已使用的堆总数为10,885,349K,已使用的Young Generation份额为613,404K。这意味着老年代的份额为10,271,945K。收集之后,新生代使用量减少了545,336K,但堆总使用量仅减少了5,195K。这意味着540,141K从新生代提升为老年代。

Full GC

现在,接下来的冗长输出包含老年代中大多数并发垃圾回收的所有不同阶段。我们将逐一检查它们,但是在这种情况下,我们将分阶段介绍日志内容,而不是一次覆盖整个事件日志,以便更简洁地表示。回顾一下,CMS收集器的整个事件如下所示:

2015-05-26T16:23:07.321-0200: 64.425: [GC (CMS Initial Mark) [CMS-initial-mark: 10812086K(11901376K)] 10887844K(12514816K), 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

2015-05-26T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]

2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-mark: 0.035/0.035 secs] [Times: user=0.07 sys=0.00, real=0.03 secs]

2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]

2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]

2015-05-26T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean: 0.167/1.074 secs] [Times: user=0.20 sys=0.00, real=1.07 secs]

2015-05-26T16:23:08.447-0200: 65.550: [GC (CMS Final Remark) [YG occupancy: 387920 K (613440 K)]65.550: [Rescan (parallel) , 0.0085125 secs]65.559: [weak refs processing, 0.0000243 secs]65.559: [class unloading, 0.0013120 secs]65.560: [scrub symbol table, 0.0008345 secs]65.561: [scrub string table, 0.0001759 secs][1 CMS-remark: 10812086K(11901376K)] 11200006K(12514816K), 0.0110730 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]

2015-05-26T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]

2015-05-26T16:23:08.485-0200: 65.588: [CMS-concurrent-sweep: 0.027/0.027 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]

2015-05-26T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]

2015-05-26T16:23:08.497-0200: 65.601: [CMS-concurrent-reset: 0.012/0.012 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

切记:在现实世界中,新生代的Minor GC可以在同时收集老年代的任何时间进行。在这种情况下,下面看到的主要收集记录将与上一章节中提到的Minor GC事件交织在一起。

阶段1:初始标记。这是CMS期间的两大停摆事件之一。此阶段的目标是标记老年代中的所有对象,这些对象要么是直接的GC Root,要么是从新生代中的某些活动对象引用的。 后者很重要,因为老年代是单独收集的。

           

  1. [ 2015-05-26T16:23:07.321-0200:64.42 ] – GC事件启动的时间,包括时钟时间和相对于JVM启动的时间。在接下来的阶段中,整个事件都使用相同的概念,因此为了简洁起见,将其跳过。
  2. [ CMS Initial Mark ]–收集阶段–在这种情况下为“初始标记”–正在收集所有GC Root。
  3. [ 10812086K ] –当前使用的老年代大小。
  4. [(11901376K)]–老年代中的总可用内存。
  5. [ 10887844K ] –当前使用的堆大小
  6. [(12514816K) ] –可用堆总数
  7. [ 0.0001997 secs ] [ Times: user=0.00 sys=0.00, real=0.00 secs ] –阶段的持续时间,也以用户,系统和实时度量。

阶段2:并发标记。在此阶段,垃圾收集器从“Initial Mark”的上一阶段中发现的根开始,遍历老年代并标记所有有生命的物体。顾名思义,“并发标记”阶段与您的应用程序同时运行,并且不会停止应用程序线程。请注意,并非老年代中的所有活动对象都可以被标记,因为应用程序在标记过程中会更改引用。

         

     在图中,与标记线程同时删除了一个指向“当前对象”的参考。

2015-05-26T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]

2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-mark: 035/0.035 secs] [Times: user=0.07 sys=0.00, real=0.03 secs]

  1. [ CMS-concurrent-mark ] –收集阶段–在这种情况下为“并发标记”–遍历老年代并标记所有活动对象。
  2. [ 035 / 0.035 secs ]–阶段的持续时间,分别显示经过的时间和挂钟时间。
  3. [ Times: user=0.07 sys=0.00, real=0.03 secs ] –“时间”部分对并发阶段意义不大,因为它是从并发标记开始测量的,并且不仅包括并发标记所做的工作。

阶段3:并发预清理。这又是一个并发阶段,与应用程序线程并行运行,而不是停止它们。当上一阶段与应用程序同时运行时,某些引用已更改。每当发生这种情况时,JVM会将包含突变对象的堆区域(称为“卡”)标记为“脏”(这称为卡标记)。

在预清洁阶段,应对这些脏物进行处理,并标出可到达的脏物。 完成后,将清洁卡。

此外,还执行了一些必要的整理工作和“最后备注”阶段的准备工作。

2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]

2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

  1. [ CMS-concurrent-preclean ] –收集阶段–在这种情况下为“Concurrent Preclean” –考虑到在上一个标记阶段中已更改参考。
  2. [ 0.016 / 0.016 secs ]–该阶段的持续时间,分别显示经过的时间和挂钟时间。
  3. [ Times: user=0.02 sys=0.00, real=0.02 secs ] –“时间”部分对并发阶段意义不大,因为它是从并发标记开始测量的,并且不仅包括并发标记所做的工作 。

阶段4:并发可终止预清理。同样,并发阶段不会停止应用程序的线程。这是一个尝试,以尽量减少停靠世界的“最后评论”的工作。此阶段的确切持续时间取决于许多因素,因为它会迭代执行相同的操作,直到满足一个中止条件(例如,迭代次数,完成的有用工作量,经过的挂钟时间等)。

2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]

2015-05-26T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean1: 0.167/1.074 secs] [Times: user=0.20 sys=0.00, real=1.07 secs]

  1. [ CMS-concurrent-abortable-preclean ] –在这种情况下,“并发可终止预清理”阶段
  2. [ 0.167 / 1.074 secs ]–该阶段的持续时间,分别显示经过时间和挂钟时间。有趣的是,报告的用户时间比时钟时间小得多。通常,我们已经看到实时时间少于用户时间,这意味着某些工作是并行进行的,因此经过的时钟时间小于所使用的CPU时间。在这里,我们需要做的工作很少-CPU时间为0.167秒,垃圾收集器线程正在等待大量时间。从本质上讲,他们试图在必须暂停STW之前尽可能地避开。默认情况下,此阶段最多可持续5秒钟。
  3. [ Times: user=0.20 sys=0.00, real=1.07 secs ] –“时间”部分对并发阶段意义不大,因为它是从并发标记开始测量的,并且不仅仅包括并发阶段所做的工作标记。

此阶段可能会严重影响即将到来的STW的持续时间,并且具有很多非常规的配置选项和故障模式。

阶段5:结束语。这是活动期间的第二个也是最后一个STW阶段。这个阶段的目标是最终标记老年代中的所有活动对象。由于先前的预清理阶段是并发的,因此它们可能无法跟上应用程序的更改速度。需要STW才能完成工作。

通常,CMS会尝试在Young Generation尽可能空的时候运行最后的备注阶段,以消除可能出现的多个停止阶段相继发生的可能性。

此事件看起来比以前的阶段复杂一些:

2015-05-26T16:23:08.447-0200: 65.550: [GC (CMS Final Remark) [YG occupancy: 387920 K (613440 K)]65.550: [Rescan (parallel) , 0.0085125 secs]465.559: [weak refs processing, 0.0000243 secs]65.5595: [class unloading, 0.0013120 secs]65.5606: [scrub string table, 0.0001759 secs7][1 CMS-remark: 10812086K(11901376K)] 11200006K(12514816K), 0.0110730 secs] [[Times: user=0.06 sys=0.00, real=0.01 secs]

  1. [ 2015-05-26T16:23:08.447-0200:65.550 ] – GC事件启动的时间,包括时钟时间和相对于JVM启动的时间。
  2. [ CMS Final Remark ]–收集阶段–在这种情况下为“最终备注” –标记旧一代中的所有活动对象,包括在先前的并发标记阶段中创建/修改的参考。
  3. [ YG occupancy: 387920 K (613440 K) ]–年轻一代的当前占用和容量。
  4. [ Rescan (parallel) , 0.0085125 secs ] –在应用程序停止时,“重新扫描”完成了对活动对象的标记。在这种情况下,重新扫描是并行进行的,花费了0.0085125秒。
  5. [ weak refs processing, 0.0000243 secs] –第一个子阶段正在处理弱引用以及该阶段的持续时间和时间戳。
  6. [ class unloading,0.0013120 secs] –下一个子阶段正在卸载未使用的类,带有该阶段的持续时间和时间戳。
  7. [ scrub string table, 0.0001759 secs ]–最后的子阶段是清理符号和字符串表,它们分别保存了类级元数据和内部化的字符串。暂停的时钟时间也包括在内。
  8. [ 10812086K(11901376K)]–此阶段之后的占用率和旧一代的容量。
  9. [ 11200006K(12514816K)]–该阶段之后的使用情况和总堆的容量。
  10. [ 0.0110730 secs ]-该阶段的持续时间。
  11. [ Times: user=0.06 sys=0.00, real=0.01 secs ] –暂停持续时间,以用户,系统和实时类别衡量。

在五个标记阶段之后,标记了旧世代中的所有活动对象,现在垃圾收集器将通过清除老年代来回收所有未使用的对象:

阶段6:并行扫描。与应用程序同时执行,而无需STW。该阶段的目的是删除未使用的对象,并回收它们所占用的空间以备将来使用。

         

2015-05-26T16:23:08.458-0200:65.561:[CMS-concurrent-sweep-start] 2015-05-26T16:23:08.485-0200:65.588:[CMS-concurrent-sweep:0.027/0.027 secs] 

[[Times: user=0.03 sys=0.00, real=0.03 secs]

  1. [ CMS-concurrent-sweep ] –在此情况下,集合“并发扫描”的阶段,将未标记并因此未使用的对象清除以回收空间。
  2. [ 0.027 / 0.027 secs ]–阶段的持续时间,分别显示经过的时间和挂钟时间。
  3. [ Times: user=0.03 sys=0.00, real=0.03 secs ] –“时间”部分在并发阶段意义不大,因为它是从并发标记的开始测量的,并且不仅仅包括并发标记的工作 。

阶段7:并行复位。 并发执行阶段,重置CMS算法的内部数据结构,并为下一个周期做好准备。

2015-05-26T16:23:08.485-0200:65.589:[CMS-concurrent-reset-start] 2015-05-26T16:23:08.497-0200: 65.601: [CMS-concurrent-reset: 0.012/0.012 secs] [[Times: user=0.01 sys=0.00, real=0.01 secs]

  1. [ CMS-concurrent-reset ] –收集阶段–在这种情况下为“并发重置”,即重置CMS算法的内部数据结构并为下一个收集做准备。
  2. [ 0.012 / 0.012 secs ]–该阶段的持续时间,分别测量经过时间和挂钟时间。
  3. [ Times: user=0.01 sys=0.00, real=0.01 secs ] –“时间”部分在并发阶段意义不大,因为它是从并发标记开始测量的,并且不仅仅包括为并发完成的工作标记。

总而言之,CMS垃圾收集器通过将大量工作卸载到不需要停止应用程序的并发线程上,在减少暂停时间方面做得很好。 但是,它也有一些缺点,其中最明显的是老年代碎片,并且在某些情况下,尤其是在大堆上,暂停持续时间缺乏可预测性。

总结

      在任何一个版本的JDK中,CMS均不是默认垃圾收集器。

      因为,CMS具有一些缺点:

  1. 内存碎片化
  2. 产生大量浮动垃圾
  3. Full GC采用Serial Old进行垃圾收集,串行效率较低,不适合大堆内存
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CMS(Concurrent Mark Sweep)是一种并发的垃圾回收器,它的主要优势是尽可能地减少应用程序的停顿时间。下面是CMS垃圾回收器的实现原理: 1. 初始标记(Initial Mark):这个阶段会暂停应用程序的线程,标记出所有的根对象,即 GC Roots,比如静态变量和活动线程等。这个阶段很快就完成了,但是应用程序会被短暂地暂停。 2. 并发标记(Concurrent Mark):在这个阶段,垃圾回收器会扫描堆中的对象,标记出所有活跃的对象,这个阶段与应用程序是并发执行的,所以不会停顿程序执行。因为这个阶段与应用程序并发执行,所以在这个阶段,堆中的对象可能会发生变化,因此需要在下一个阶段扫描时重新确认它们是否仍然是活跃的。 3. 重新标记(Remark):在这个阶段,垃圾回收器会重新遍历堆中的对象,标记出在并发标记阶段发生变化的对象。这个阶段需要暂停应用程序,但是它的时间通常比初始标记阶段更短。 4. 并发清除(Concurrent Sweep):在这个阶段,垃圾回收器会扫描堆中的对象,清除所有被标记为垃圾的对象。这个阶段与应用程序是并发执行的,因此不会停顿程序执行。由于在清除对象时不需要移动对象,因此这个阶段的性能通常比标记阶段更高。 需要注意的是,CMS垃圾回收器并不会移动对象,因此当堆中的对象达到一定的程度时,会出现碎片化的问题。为了解决这个问题,JVM还提供了G1(Garbage First)垃圾回收器。 另外,CMS垃圾回收器虽然能够减少应用程序的停顿时间,但是在执行过程中会产生一些额外的负载,因此需要根据应用程序的特点和需求来选择合适的垃圾回收器。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值