CMS垃圾回收和线上Full GC排查

背景

咱们上线Java服务的时候须要对其配置一些JVM参数,如堆空间大小、虚拟机栈大小、垃圾回收算法。对于年轻代和老年代咱们能够配置不一样的垃圾回收算法。在一些对rt要求很高的场景,服务不能有长时间的卡顿,CMS就能够运用于此场景。算法

Concurrent Mark Sweep,是一款基于并发、使用标记清除算法的垃圾回收算法,只针对老年代进行垃圾回收。CMS收集器工做时,GC工做线程和用户线程能够并发执行,以达到下降STW时间的目的。segmentfault

开起VM选项-XX:+UseConcMarkSweepGC,表示对老年代的回收采用CMS。安全

前置知识

STW

首先,咱们须要厘清一个概念,即只有标记阶段才须要STW。标记完成之后,须要清除的对象已经肯定,不管此时是否产生新的垃圾,都不影响对这些对象的清理。也就是说,清除阶段是能够设计成和用户线程并发执行的。数据结构

JVM在暂停的时候,须要选准一个时机,因为JVM系统运行期间的复杂性,不可能作到随时暂停,所以引入了安全点(safepoint)的概念:程序只有在运行到安全点的时候,才能够暂停下来。HotSpot采用主动中断的方式,让执行线程在运行期轮询是否须要暂停的标志,若须要则中断挂起。HotSpot使用了几条短小精炼的汇编指令即可完成安全点轮询以及触发线程中断,所以对系统性能的影响几乎能够忽略不计。并发

可达性

可达性是指,若是一个对象会被至少一个程序中的可达对象经过直接或间接的方式引用,则称该对象是可达的。更详细地说,一个对象知足一下两个条件之一,即被断定为可达的。性能

1.自己是根对象。根(root)是指由堆之外空间访问的对象。JVM会将如下对象标记为根:a.虚拟机栈(栈帧中的本地变量表)中引用的对象;b.方法区中的类静态属性引用的对象;c.方法区中的常量引用的对象;d.本地方法栈中JNI的引用对象。spa

2.被一个可达的对象引用。线程

CMS的几个阶段

CMS将可达性分析分解成两个阶段:a.仅扫描与根节点直接关联的对象; b.继续向下扫描完全部对象。所以,标记阶段也被拆分红两个阶段,即初始标记并发标记设计

CMS完整的收集过程以下:日志

  1. 初始标记(init-mark):仅扫描与根节点直接关联的对象并标记,这个阶段必须STW, 因为跟节点数量有限,因此这个过程很是短暂。
  2. 并发标记(concurrent-marking):与用户线程并发标记。这个阶段在初始标记的基础上继续向下追溯标记。在并发标记阶段,用户线程和标记线程并发执行,因此用户不会感觉到停顿。
  3. 并发预清理(concurrent-precleaning):与用户线程并发进行。在并发标记阶段一些对象的引用已经发生了变化,precleaning会发现这些引用关系的改变,并将存活的对象标记。举个例子:若是线程A有一个指向对象X的引用,并将该引用传递给了线程B,CMS须要记录下线程B持有了对象X,即便线程A已经不存在了。precleaning是为了减小下一阶段“从新标记”的工做量,由于remark阶段会STW
  4. 从新标记(remark) remark阶段会STW。若是应用正在并发运行且在不断地改变对象引用,CMS则不能准确地肯定某个对象是否存活。因此CMS会在remark阶段STW,从而获取全部引用关系的改变。
  5. 并发清理(concurrent-sweeping):清理垃圾对象,这个阶段GC线程和用户线程并发执行。
  6. 并发重置(concurrent-reset):重置CMS收集器的数据结构,作好下一次执行GC任务的准备工做。

alt text

线上Full GC分析

线上某服务的老年代配置了CMS,但却在gc.log发现连续Full GC的问题。JVM参数配置以下:

-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=68

参数的意义是:在老年代到68%的时候,会触发一次CMS GC,应该是出现相似以下的日志:

T20:10:37.803+0800: 3246087.559: [CMS-concurrent-mark-start]
T20:10:38.463+0800: 3246088.220: [CMS-concurrent-mark: 0.661/0.661 secs] [Times: user=3.17 sys=0.56, real=0.66 secs]
T20:10:38.463+0800: 3246088.220: [CMS-concurrent-preclean-start]
T20:10:38.552+0800: 3246088.309: [CMS-concurrent-preclean: 0.069/0.089 secs] [Times: user=0.14 sys=0.04, real=0.09 secs]_</span>
T20:10:38.552+0800: 3246088.309: [CMS-concurrent-abortable-preclean-start]

但线上环境的日志却出现以下的状况:

alt text

老年代配置了900M,但却在只使用了50+M的时候触发了Full GC,并且是在短暂的时间内连续触发。

配置了CMS却触发Full GC,有如下几种可能:

  1. 大对象分配时,年轻代不够,直接晋升到老年代,老年代空间也不够,触发 Full GC(老年代还剩800+M,显然不可能)
  2. 内存碎片致使(因为CMS是基于标记清除算法的,全部会致使内存碎片,但经过grep -i "cms" gc.log,JVM还没有触发过CMS回收,因此也不存在内存碎片的说法)
  3. CMS GC失败致使(从gc.log并未找到concurrent mode failure的记录,排除)
  4. jmap -histo(人为执行该命令)

经笔者回忆,在中午快12点的时候确实登陆过线上机,执行过jmap -histo:live命令,经验证,手动执行jmap -histo:live,也确实会在gc.log出现触发 Full GC的现象,问题获得验证。

原文连接

https://segmentfault.com/a/11...

相关文章
相关标签/搜索