1 CMS垃圾收集器介绍

CMS(Concurrent Mark Sweep)收集器旨在获取最短回收停顿时间的并发垃圾收集器。CMS基于“标记-清除”算法实现,并发指的是CMS的垃圾收集线程与用户线程可同时执行,但并不是没有STW,而是追求STW时间很短,尽可能地并发( Mostly-Concurrent)执行垃圾收集任务 。默认使用ParNew回收器作为新生代垃圾收集器。

CMS垃圾回收器将垃圾回收分成7个阶段,某些阶段可以和用户线程并发执行,并发阶段主要做准备工作,这些准备工作能缩小需要STW阶段的停顿时间。

CMS适用于对响应时间敏感的,堆大小适中(不可太大,也不能太小),同时服务器CPU核数较多的应用。

2 GC日志参数

‐XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution ‐Xloggc:

-XX:+PrintGC用于打印Minor GC跟Full GC的简单日志,由于打印的日志太过简单,一般不使用。因此,通常使用-XX:PrintGCDetails来打印详细GC日志。

使用-XX:+PrintTenuringDistribution可以在Minor GC的时候同时跟踪Survivor区中的对象的年龄分布情况。

3 Minor GC

3.1 一个典型的Minor GC(ParNew收集器)日志分析

2020-10-27T15:32:23.284+0800: 345720.604: [GC (Allocation Failure) 2020-10-27T15:32:23.284+0800: 345720.604: [ParNewDesired survivor size 214728704 bytes, new threshold 4 (max 4)- age   1:    1033064 bytes,    1033064 total- age   2:     531672 bytes,    1564736 total- age   3:       2816 bytes,    1567552 total- age   4:       1792 bytes,    1569344 total: 3357713K->2296K(3774912K), 0.0047297 secs] 5464733K->2109321K(7969216K), 0.0048541 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
  • 2020-10-27T15:32:23.284+0800 — GC事件开始的标准时间,其中 +0800 表示东8区。

  • 345720.604 — GC事件开始时,相对于JVM启动的时间间隔,单位是秒。

  • GC — 用于区分Minor GC还是Full GC。GC表示这是一次Minor GC

  • Allocation Failure — 触发GC的原因。这次GC事件是由于没有适当的空间来存在新的对象引起的。

  • ParNew — 垃圾收集器名称。我们知道这个新生代垃圾收集器是跟老年代CMS垃圾收集器配套使用的,特点是使用并发标记-复制算法,会有STW。

  • Desired survivor size 214728704 bytes, new threshold 4 (max 4) — Desired survivor size值用于动态计算年龄晋升阈值。new threshold 4 (max 4),动态计算后的晋升阈值

动态计算晋升阈值:除了第一次GC之后,JVM会自动计算晋升阈值(0~15之间),但不会超过这个值。-XX:MaxTenuringThreshold — 新生代对象最多经过多少次YGC存活后会晋升到老生代,请注意是最多而不是一定。CMS中这个值默认是6,JVM使用4个bit来存储age,因此最大可以设置15。-XX:TargetSurvivorRatio — 指定Survivor中存活大小(Desired survivor size)最多占用比例,默认50,也就是50%。Desired survivor size = (survivor大小 * TargetSurvivorRatio) / 100,回看上面的例子,Desired survivor size 214728704 bytes = 200M,可以推算出survivor大小是400M动态计算晋升阈值逻辑:遍历所有对象根据年龄分组,对所占用的大小进行累计,如果某个年龄累计大小超过survivor区的一半,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值。
  • age   1 ~ 4:    XXX bytes,    XXX total — Survivor区中的对象的年龄分布情况。

  • 3357713K->2296K — 在垃圾收集前后的新生代内存使用情况。

  • (3774912K) — 年轻代总大小

  • 0.0047297 secs — 垃圾收集器在 w/o final cleanup 阶段消耗的时间

  • 5464733K->2109321K — 在垃圾收集前后堆内存的使用情况。

  • (7969216K) —  可用堆总大小

  • 0.0048541 secs — 垃圾收集器在标记和复制年轻代存活对象时所消耗的时间。包括和CMS收集器的通信开销, 复制存活时间达到阈值的对象到老年代,以及垃圾收集后期的最终清理。

  • [Times: user=0.02 sys=0.00, real=0.01 secs] — GC事件执行时长。

    • user — GC线程消耗的总CPU时间。

    • sys — GC过程中操作系统调用和系统等待总消耗时间。

    • real — STW时间。正常情况下约等于 (user + sys) / GC线程数

-XX:ParallelGCThreads表示的是GC并行线程数,如果新生代使用ParNew,那么ParallelGCThreads就是新生代GC线程数。默认情况下,当CPU数量小于8时,ParallelGCThreads的值就是CPU的数量,当CPU数量大于8时,ParallelGCThreads的值等于 3+ 5 * cpuCount / 8。ParallelGCThreads = (ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8)

从Minor GC日志我们可以推算这次GC后晋升到老年代的对象大小。

上面的例子中,在Minor GC之前,堆内存总使用量为5464733K,其中新生代为3357713K,同时计算出老年代使用了2107020K。

在Minor GC之后, 新生代使用量减少为3355417K, 但总的堆内存使用量只减少了3355412K 。这表示有大小为 5K 的对象从年轻代提升到老年代。

4 CMS垃圾回收

上面已经讲到CMS收集器一个周期分为7个阶段,分别是:

  • 初始标记(CMS initial mark)

  • 并发标记(CMS concurrent mark)

  • 并发预清理(CMS concurrent-preclean)

  • 可中止并发预清理 (CMS concurrent-abortable-preclean)

  • 重标记(CMS Final remark)

  • 并发清除(CMS concurrent sweep)

  • 并发重置(CMS concurrent-reset)

4.1 一个典型的正常的CMS GC日志

2020-10-25T20:17:59.799+0800: 292915.748: [GC (CMS Initial Mark) [1 CMS-initial-mark: 3145876K(4194304K)] 3149003K(7969216K), 0.0037290 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]2020-10-25T20:17:59.803+0800: 292915.752: [CMS-concurrent-mark-start]2020-10-25T20:17:59.974+0800: 292915.924: [CMS-concurrent-mark: 0.172/0.172 secs] [Times: user=1.37 sys=0.35, real=0.18 secs]2020-10-25T20:17:59.975+0800: 292915.924: [CMS-concurrent-preclean-start]2020-10-25T20:18:00.019+0800: 292915.968: [CMS-concurrent-preclean: 0.044/0.044 secs] [Times: user=0.10 sys=0.02, real=0.04 secs]2020-10-25T20:18:00.019+0800: 292915.969: [CMS-concurrent-abortable-preclean-start]CMS: abort preclean due to time 2020-10-25T20:18:05.036+0800: 292920.985: [CMS-concurrent-abortable-preclean: 4.994/5.017 secs] [Times: user=11.77 sys=2.42, real=5.02 secs]2020-10-25T20:18:05.039+0800: 292920.989: [GC (CMS Final Remark) [YG occupancy: 1020038 K (3774912 K)] 2020-10-25T20:18:05.078+0800: 292921.027: [Rescan (parallel) , 0.0119305 secs]2020-10-25T20:18:05.090+0800: 292921.039: [weak refs processing, 0.1948392 secs] 2020-10-25T20:18:05.284+0800: 292921.234: [class unloading, 0.0713296 secs] 2020-10-25T20:18:05.356+0800: 292921.305: [scrub symbol table, 0.0329587 secs] 2020-10-25T20:18:05.389+0800: 292921.338: [scrub string table, 0.0042639 secs] [1 CMS-remark: 3145964K(4194304K)] 3149210K(7969216K), 0.3588411 secs] [Times: user=1.96 sys=0.29, real=0.36 secs]2020-10-25T20:18:05.399+0800: 292921.348: [CMS-concurrent-sweep-start]2020-10-25T20:18:09.099+0800: 292925.049: [CMS-concurrent-sweep: 3.701/3.701 secs] [Times: user=10.30 sys=2.31, real=3.70 secs]2020-10-25T20:18:09.100+0800: 292925.049: [CMS-concurrent-reset-start]2020-10-25T20:18:09.135+0800: 292925.084: [CMS-concurrent-reset: 0.035/0.035 secs] [Times: user=0.09 sys=0.02, real=0.04 secs]

4.2 结合CMS日志分析CMS收集器执行过程

阶段1 - 初始标记(STW)

初始化标记阶段进行可达性分析,标记老年代中的GC ROOT直接引用的对象以及老年代中被新生代(存活对象)直接引用的对象。这里简单提一句GC Roots是一组活跃的引用,而不是活跃的对象本身。

2020-10-25T20:17:59.799+0800: 292915.748: [GC (CMS Initial Mark) [1 CMS-initial-mark: 3145876K(4194304K)] 3149003K(7969216K), 0.0037290 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]* CMS Initial Mark — 初始化标记阶段用于收集老年代中所有GC Root直接引用的对象与被新生代直接引用的对象* 3145876K — 当前老年代使用大小* 4194304K — 老年代总大小* 3149003K — 当前堆总体使用大小* 7969216K — 可用堆总大小* 0.0037290 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] — 这个阶段持续时间

这个阶段是STW的,可以通过开启并发初始化标记 -XX:+CMSParallelInitialMarkEnabled ,并调整并发标记线程数。

阶段2 - 并发标记

从第1阶段收集到的对象引用开始,遍历标记所有的对象引用。这个阶段是并行执行的不会STW,而由于是跟程序线程并行执行的,因此在这个阶段过程中会有新的对象产生而没被标记到,或者是引用关系变化而导致标记了需要回收的对象。也是这个原因需要进行重新标记。

上面的图表明,在标记线程运行过程中,Current obj的引用关系发生了改变

2020-10-25T20:17:59.803+0800: 292915.752: [CMS-concurrent-mark-start]2020-10-25T20:17:59.974+0800: 292915.924: [CMS-concurrent-mark: 0.172/0.172 secs] [Times: user=1.37 sys=0.35, real=0.18 secs]* CMS-concurrent-mark — 并发标记阶段遍历并标记老年代所有活跃对象* 0.172/0.172 secs] [Times: user=1.37 sys=0.35, real=0.18 secs] — 这个阶段持续时间

阶段3 - 并发预清理

由于在并发标记阶段标记线程跟程序线程并行执行,从而会导致引用关系变化,需要重新标记这些对象。但是如果重新遍历老年代对象的代价太高,因此为了提高效率,在并发标记阶段会将“变化”的对象所在的Card标记为Dirty,在并发预清理阶段只需扫描Dirty card包含的对象,避免遍历老年代(Card Marking)。

在并发预处理阶段会从Dirty card包含的对象开始标记可达对象,并在完成后重置Dirty card标记位。

2020-10-25T20:17:59.975+0800: 292915.924: [CMS-concurrent-preclean-start]2020-10-25T20:18:00.019+0800: 292915.968: [CMS-concurrent-preclean: 0.044/0.044 secs] [Times: user=0.10 sys=0.02, real=0.04 secs]* CMS-concurrent-preclean — 并发预清理处理上一个阶段引用更改的对象* 0.172/0.172 secs] [Times: user=1.37 sys=0.35, real=0.18 secs] — 这个阶段持续时间

阶段4 - 可中止并发预清理

这个阶段的目标跟“并发预清理”阶段相同,都是为了减少重标记阶段的工作量。这个阶段的目的是:在进入重标记(STW)之前尽可能等待一次Minor GC,从而减少重标记阶段的停顿时间。

-XX:+CMSScheduleRemarkEdenSizeThreshold — 当eden区使用超过 -XX:+CMSScheduleRemarkEdenSizeThreshold(默认2M)时可以进入可中止的并发预清理阶段,否则直接进入重标记(STW)阶段。-XX:CMSScheduleRemarkEdenPenetration — 在可中止并发清理阶段执行过程中,当eden区使用率超过 -XX:CMSScheduleRemarkEdenPenetration(默认50%)时中断。因为这个阶段是在等待一次Minor GC,但GC是JVM自动调度的,无法预知会在多长时间之内必定会出现一次Minor GC,因此在新生代对象创建速率很慢的情况下,需要有其他机制来中止等待。-XX:CMSMaxAbortablePrecleanLoops — 默认是0,如果设置了会检查循环次数,在大于设置值的时候中止。日志会出现"CMS: abort preclean due to loops"-XX:CMSMaxAbortablePrecleanTime — 最大执行时间,默认5s,超过时中止循环。日志会出现"CMS: abort preclean due to time"那么如果在这个阶段没能等到一次Minor GC,也可以通过设置 -XX:+CMSScavengeBeforeRemark 在进入重标记(STW)之前强制执行一次Minor GC。如果没有特殊情况,没有必要特意打开,打开后虽然减少了重标记(STW)阶段的停顿时间,但是这样会在Minor GC的STW之后紧跟着一个重标记的STW。
2020-10-25T20:18:00.019+0800: 292915.969: [CMS-concurrent-abortable-preclean-start]CMS: abort preclean due to time 2020-10-25T20:18:05.036+0800: 292920.985: [CMS-concurrent-abortable-preclean: 4.994/5.017 secs] [Times: user=11.77 sys=2.42, real=5.02 secs]* CMS-concurrent-abortable-preclean — 可中止并发预清理等待一次Minor GC减少重标记时的停顿* 4.994/5.017 secs] [Times: user=11.77 sys=2.42, real=5.02 secs] — 这个阶段持续时间

阶段5 - 重标记(STW)

由于前面几个阶段都是并发的,因此会出现对象引用的变化但没有被感知,重标记会重新扫描堆中对象,标记所有引用发生变化的对象,因此扫描包括GC Root + 新生代 + dirty card包含的老年代对象。如果预清理阶段工作做得比较好能很好的减少停顿时间。

2020-10-25T20:18:05.039+0800: 292920.989: [GC (CMS Final Remark) [YG occupancy: 1020038 K (3774912 K)] 2020-10-25T20:18:05.078+0800: 292921.027: [Rescan (parallel) , 0.0119305 secs]2020-10-25T20:18:05.090+0800: 292921.039: [weak refs processing, 0.1948392 secs] 2020-10-25T20:18:05.284+0800: 292921.234: [class unloading, 0.0713296 secs] 2020-10-25T20:18:05.356+0800: 292921.305: [scrub symbol table, 0.0329587 secs] 2020-10-25T20:18:05.389+0800: 292921.338: [scrub string table, 0.0042639 secs] [1 CMS-remark: 3145964K(4194304K)] 3149210K(7969216K), 0.3588411 secs] [Times: user=1.96 sys=0.29, real=0.36 secs]* CMS Final Remark — 重新标记老年代所有活跃对象* YG occupancy: 1020038 K (3774912 K) — 当前新生代的使用率跟大小* Rescan (parallel) , 0.0119305 secs — 在这个小阶段中会STW并并发标记活跃对象,总共花了0.0119305秒* weak refs processing, 0.1948392 secs — 处理弱引用花了0.1948392秒* class unloading, 0.0713296 secs — unloading没用的class花了0.0713296秒* scrub symbol table, 0.0329587 secs — 清理StringTable及SymbolTable花了0.0329587秒* 3145964K(4194304K) — 这个阶段后老年代的使用情况* 3149210K(7969216K) — 这个阶段后堆的使用情况* 0.3588411 secs] [Times: user=1.96 sys=0.29, real=0.36 secs] — 这个阶段持续时间

阶段6 - 并发清理

这个阶段的目标是清理不用的对象,回收它们占用的空间。由于是跟应用线程并发执行,因此会有新的垃圾对象产生,新的垃圾对象在这次GC过程中不会被清理,这些垃圾对象称为“浮动垃圾”。

2020-10-25T20:18:05.399+0800: 292921.348: [CMS-concurrent-sweep-start]2020-10-25T20:18:09.099+0800: 292925.049: [CMS-concurrent-sweep: 3.701/3.701 secs] [Times: user=10.30 sys=2.31, real=3.70 secs]* CMS-concurrent-sweep — 阶段名称* 3.701/3.701 secs] [Times: user=10.30 sys=2.31, real=3.70 secs] — 这个阶段持续时间

阶段7 - 并发重置

这个阶段会重置CMS内部数据结构与状态,等待下一个回收周期。

2020-10-25T20:18:09.100+0800: 292925.049: [CMS-concurrent-reset-start]2020-10-25T20:18:09.135+0800: 292925.084: [CMS-concurrent-reset: 0.035/0.035 secs] [Times: user=0.09 sys=0.02, real=0.04 secs]* CMS-concurrent-reset— 阶段名称* 0.035/0.035 secs] [Times: user=0.09 sys=0.02, real=0.04 secs] — 这个阶段持续时间

4.3 CMS异常情况

堆碎片

CMS垃圾收集器在清理垃圾对象之后,并没有任何碎片整理机制。这就会引出一个问题,虽然堆实际使用较小,但没有连续空间可以分配来容纳新对象,一旦出现这种情况就会触发Full GC,而Full GC需要较长时间的STW。

对象分配速率过快

在CMS的并发执行阶段,用户线程继续执行创建新对象,当此时触发了Minor GC或者分配大对象到老年代(也就是浮动垃圾),老年代没有足够的可用空间来容纳晋升对象,就会触发concurrent mode failure,CMS会退化成Full GC(old区会执行一次没有并发阶段的CMS)。

避免concurrent mode failure可以通过以下两个参数配合用于控制CMS触发时机、频率,减少GC停顿时间。设置-XX:CMSInitiatingOccupancyFraction必须谨慎,过小会导致频繁出现CMS回收。

也可以通过提高CMS收集线程数来提高回收速度。

-XX:CMSInitiatingOccupancyFraction=75 — 对内存占用率达到75%的时候触发GC(存在浮动垃圾,所以需要保留一定的空间)。-XX:+UseCMSInitiatingOccupancyOnly — 指定需要使用-XX:CMSInitiatingOccupancyFraction的值。默认不打开,不打开的情况下JVM会在第一次使用-XX:CMSInitiatingOccupancyFraction之后动态调整。-XX:+CMSConcurrentMTEnabled -XX:ConcGCThreads=4ConcGCThreads默认计算逻辑ParallelGCThreads = (ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8)  (ncpus为cpu个数)ConcGCThreads = (ParallelGCThreads + 3) / 4

上面提到CMS没有进行碎片整理,所以还提供了碎片压缩机制,相关的两个参数表示说经多少次Full GC之后执行一次标记-清理-压缩(Mark Sweep Compact)操作。-XX:+UseCMSCompactAtFullCollection默认打开,-XX:CMSFullGCsBeforeCompaction默认是0,意味着每次Full GC后都执行一次标记-清理-压缩。

-XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction=0

0 full gc时cpu idle_结合GC日志讲讲CMS垃圾收集器相关推荐

  1. 0 full gc时cpu idle_【cpuidle】计算每个cpu进入idle的时间

    参考内核文档 Supporting multiple CPU idle levels in kernel cpuidle sysfs System global cpuidle related inf ...

  2. 0 full gc时cpu idle_Go语言中如何观察GC

    我们以下面的程序为例,先使用四种不同的方式来介绍如何观察 GC,并在后面的问题中通过几个详细的例子再来讨论如何优化 GC. package mainfunc allocate() { _ = make ...

  3. java gc时自动收dump_Full GC分析:设置Java VM参数实现在Full GC前后自动生成Dump

    本文讲解了如何设置JavaVM参数实现在Full GC前后自动生成Dump.共有三个VM参数需要设置: HeapDumpBeforeFullGC 实现在Full GC前dump. HeapDumpBe ...

  4. JVM面试(四)-垃圾回收、垃圾收集器、GC日志

    垃圾回收.垃圾收集器.GC日志 什么是垃圾?(垃圾的概念) 什么是垃圾回收?(垃圾回收的概念) 为什么要垃圾回收?(垃圾回收的原因) 如何定义垃圾? 引用计数算法 什么是循环引用 可达性分析算法 哪些 ...

  5. JVM初探:内存分配、GC原理与垃圾收集器

    JVM内存的分配与回收大致可分为如下4个步骤: 何时分配 -> 怎样分配 -> 何时回收 -> 怎样回收. 除了在概念上可简单认为new时分配外, 我们着重介绍后面的3个步骤: I. ...

  6. 再谈GC2:Java垃圾收集器与GC日志分析实践

    4. GC 算法(实现篇) - GC参考手册 2017年02月05日 23:58:36 阅读数:6862 您应该已经阅读了前面的章节: 垃圾收集简介 - GC参考手册 Java中的垃圾收集 - GC参 ...

  7. JVM初探- 内存分配、GC原理与垃圾收集器

    JVM初探- 内存分配.GC原理与垃圾收集器 标签 : JVM JVM内存的分配与回收大致可分为如下4个步骤: 何时分配 -> 怎样分配 -> 何时回收 -> 怎样回收. 除了在概念 ...

  8. GC之G1垃圾收集器

    GC之G1垃圾收集器 目录 以前收集器的特点 G1是什么 G1特点 G1底层原理 G1回收步骤 和CMS相比的优势 小总结 1. 以前收集器的特点 年轻代和老年代是各自独立且连续的内存块 年轻代收集必 ...

  9. (六)JVM成神路之GC基础篇:对象存活判定算法、GC算法、STW、GC种类详解

    引言 经过前面五个章节的分析后,对于JVM的大部分子系统都已阐述完毕,在本文中则开始对JVM的GC子系统进行全面阐述,GC机制也是JVM的重中之重,调优.监控.面试都逃不开的JVM话题. 在前面分析J ...

最新文章

  1. java aqs源码_Java-AQS源码详解(细节很多!)
  2. 前沿丨DeepMind提出神经元删除法:通过理解每个神经元来理解深度学习
  3. 日志组件logback的介绍及配置使用方法
  4. wireshark tcp抓包分析_网络分析系列之八_使用Wireshark抓包
  5. java中Map ListE的用法
  6. 开发板ping不通Linux虚拟机的原因及解决办法
  7. 阿里easyExcel学习笔记(maven)
  8. 电压和电流的有效值、瞬时值、平均值、最大值及其关系
  9. 请问王菲的<流年>歌词的含义
  10. nginx 80 443 并存
  11. Java进阶-requestresponse (十一)
  12. MSP432E4系列编码器(QEI模块)速度换算
  13. 顺丰java_Java顺丰同城接口开发
  14. 网页制作之HTML+CSS布局
  15. mysql select 补空行_用前一行的值填充空行mysql
  16. 云服务器修改dns服务器为阿里云公共dns服务器
  17. 谷粒商城十五商品详情CompletableFuture异步编排
  18. 图像处理:图片像素深度unit16位转unit8位
  19. 小程序影藏溢出的gif_你的GIF动图不够酷炫?可能是你还不知道这5个有趣的网站!...
  20. android即时通信和sns,基于Android平台的实时SNS系统设计与实现

热门文章

  1. UNIX环境高级编程之第4章:文件和文件夹-习题
  2. Windows Linux Mac 路由添加删除
  3. Highcharts使用=====通过指定日期显示曲线
  4. 嵌套循环连接(Nested Loops), 合并联接(Merge), 哈希联接(Hash)的适用情况
  5. java se开发工具_JavaSE基础代码(1)-Hi ShanShi与开发工具
  6. java常量数组吗_java – 如何在注释中使用数组常量
  7. 【写作技巧】毕业论文格式要求
  8. 王校长撩妹不成反被锤爆?再有钱的舔狗也只是舔狗【Python爬虫实战:微博评论采取】
  9. 数学建模层次分析法例题及答案_【热门推荐】影响力意志力创新力、数学建模简明教程...
  10. 东农计算机应用与技术,东农16春《计算机应用与技术》在线作业.doc