本文翻译自:
https://blogs.oracle.com/poonam/entry/understanding_cms_gc_logs

准备工作

JVM的GC日志的主要参数包括如下几个:
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径

CMS GC知识

CMS,全称Concurrent Mark and Sweep,用于对年老代进行回收,目标是尽量减少应用的暂停时间,减少full gc发生的机率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。
CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停,内外的设置正常收集周期是这样的:

  1、CMS-initial-mark 初始标记
  2、CMS-concurrent-mark 并发标记的
  3、CMS-concurrent-preclean 执行预清理
  4、CMS-concurrent-abortable-preclean 执行可中止预清理
  5、CMS-remark 重新标记
  6、CMS-concurrent-sweep 并发清除
  7、CMS-concurrent-reset 并发重设状态等待下次CMS的触发

其中,CMS-initial-mark和CMS-remark会stop-the-world。

具体CMS的原理可以参看这篇文章
了解CMS(Concurrent Mark-Sweep)垃圾回收器

理解CMS GC日志

启动jvm的时候,增加参数-XX:+PrintGCDetails 和 -XX:+PrintGCTimeStamps可以打印出CMS GC的详细日志。读懂log信息有助于应用系统的各种参数调优,同时也有助于使得CMS实现更好的性能。

下面的例子使用了1.8的jvm

2016-12-21T15:05:30.175+0800: 90.439: [GC2016-12-21T15:05:30.175+0800: 90.439: [ParNew: 720896K->49062K(720896K), 0.2289360 secs] 864443K->262770K(1507328K), 0.2292340 secs] [Times: user=0.42 sys=0.00, real=0.23 secs]

Young generation(ParNew)收集。新生代的容量是720896K,在使用量达到了720896K时发生了GC,回收的Young generation容量为49062K。本次GC花费了0.2289360s

2016-12-21T15:05:14.628+0800: 74.892: [GC [1 CMS-initial-mark: 646746K(786432K)] 723228K(1507328K), 0.0456000 secs] [Times: user=0.04 sys=0.00, real=0.04 secs]

CMS开始回收tenured generation collection。这阶段是CMS初始化标记的阶段,从垃圾回收的“根对象”开始,且只扫描直接与“根对象”直接关联的对象,并做标记,在此期间,其他线程都会停止。

tenured generation的空间是786432K,在容量为646746K时开始执行初始标记。


2016-12-21T15:05:14.674+0800: 74.938: [CMS-concurrent-mark-start]

Concurrent marking(并发标记)阶段开始。
本阶段,其他线程重新开始正常执行,在上一步初始对象的基础上继续向下追溯标记


2016-12-21T15:05:15.188+0800: 75.452: [CMS-concurrent-mark: 0.513/0.514 secs] [Times: user=0.95 sys=0.05, real=0.52 secs]

并发标记阶段发费了0.513s cpu time 和0.514s 系统时间(包括其他线程占用cpu导致标记线程挂起的时间)


2016-12-21T15:05:15.188+0800: 75.452: [CMS-concurrent-preclean-start]

concurrent-preclean(并发域清理)阶段开始
并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World。


2016-12-21T15:05:15.192+0800: 75.456: [CMS-concurrent-preclean: 0.004/0.004 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

concurrent-preclean花费了0.004s cpu time 和 0.004s 系统时间


2016-12-21T15:05:20.256+0800: 80.521: [GC[YG occupancy: 377234 K (720896 K)]2016-12-21T15:05:20.256+0800: 80.521: [Rescan (parallel) , 0.3789280 secs]2016-12-21T15:05:20.635+0800: 80.900: [weak refs processing, 0.0017780 secs]2016-12-21T15:05:20.637+0800: 80.901: [class unloading, 0.0141220 secs]2016-12-21T15:05:20.651+0800: 80.916: [scrub symbol table, 0.0259990 secs]2016-12-21T15:05:20.677+0800: 80.942: [scrub string table, 0.0024140 secs] [1 CMS-remark: 646746K(786432K)] 1023980K(1507328K), 0.4268320 secs] [Times: user=0.78 sys=0.01, real=0.42 secs]

rescan阶段,会暂停其他线程。重新扫描CMS堆中剩余的对象,重新从“根对象”开始扫描,并且也会处理对象关联。本次扫描花费了 0.3789280s,其中弱引用对象(weak referene objects)处理大约用来0.0017780s,本阶段共计时间0.4268320s


2016-12-21T15:05:20.685+0800: 80.950: [CMS-concurrent-sweep-start]

开始清理没有标记的对象,清理阶段是和其他线程并发进行的。


2016-12-21T15:05:21.340+0800: 81.605: [CMS-concurrent-sweep: 0.640/0.655 secs] [Times: user=1.23 sys=0.03, real=0.66 secs]

时间,不解释了


2016-12-21T15:05:21.341+0800: 81.605: [CMS-concurrent-reset-start]
2016-12-21T15:05:21.402+0800: 81.666: [CMS-concurrent-reset: 0.061/0.061 secs] [Times: user=0.06 sys=0.05, real=0.06 secs]

reset 阶段开始,并且输出所用时间。在这个阶段,与CMS相关数据结构被重新初始化,这样下一个周期可以正常进行。

以上过程是一个正常的CMS GC循环周期,接下来再分析一些不正常的日志

以下这个情况我没有复现,用的是文章的中数据,TODO之后想想能否打印出来这种情况


197.976: [GC 197.976: [ParNew: 260872K->260872K(261952K), 0.0000688 secs]197.976: [CMS197.981: [CMS-concurrent-sweep: 0.516/0.531 secs]
(concurrent mode failure): 402978K->248977K(786432K), 2.3728734 secs] 663850K->248977K(1048384K), 2.3733725 secs]

这显示,ParNew收集请求执行,但是没有成功。因为此时系统估计没有老生代中没有足够的空间去容纳这些对象(预测之后可能会出现老生代的空余空间将会被系统占光),我们称这种情况为 “full promotion guarantee failure”

在这种情况下下,并发式的CMS被阻塞了,full GC执行了,GC算法进入了concurrent mode failure状态,调用一个serail Old GC(阻塞了其他线程)来清理系统的Heap

日志显示,Full GC花费了2.3733725s,老生代空间由402978K降到了248977K

concurrent mode failure可以通过增大老生代的空间或者通过设置CMSInitiatingOccupancyFraction一个小的值使得CMS Collection发生的更频繁(CMSInitiatingOccupancyFraction可以控制CMS执行的时间,假设设置为70,说明老生代在利用率为70%时发生CMS),但是把这个值设置小也会导致CMS发生更加频繁。

某些情况下,promotion failures也会发生,即使是老生代有足够的空间。这个原因是"fragmentation"-老生代的可用空间不是连续的,而将新生代的对象移动到老生代需要连续的可用空间。而CMS是不会对内存进行压缩的算法,因此造成了这种问题。TODO,这篇文章解决了这个问题,我还没读

补充一下:concurrent mode failure产生的原理:CMS并发处理阶段用户线程还在运行中,伴随着程序运行会有新的垃圾产生,CMS无法处理掉它们(没有标记),只能在下一次GC的时候处理。同样的,用户线程运行就需要分配新的内存空间,为此,CMS收集器并不会在老年代全部被填满以后在进行收集,会预留一部分空间提供并发收集时的程序运行使用。即使是这样,还是会存在CMS运行期间预留的内存无法满足程序需求,就会出现"Concurrent Mode Failure"失败,这是,虚拟机将会启动备案操作:临时启动Serial Old 收集器来重新进行老年代的垃圾收集,Serial Old收集器会Stop the world,这样会导致停顿时间过长

同样的,CMS收集结束后会有大量的碎片空间差生,也会给大对象分配带来麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够多的连续空间来分配当前对象,不得不提前触发一次Full GC


197.976: [GC 197.976: [ParNew: 260872K->260872K(261952K), 0.0000688 secs]197.976: [CMS197.981: [CMS-concurrent-sweep: 0.516/0.531 secs] (concurrent mode failure): 402978K->248977K(786432K), 2.3728734 secs] 663850K->248977K(1048384K), 2.3733725 secs]

这行GC日志显示的是一个请求失败的新生代GC处理。因为没有足够的空间来存储由新生代晋升上来的对象。这种现象称之为full promotioin guarantee failure 就此会在197.981处产生一次FULL GC,花费了 2.3733725秒 ,从而使CMS的空间从 402978K->248977K.

理解CMS GC日志的更多相关文章

  1. JVM调优——之CMS GC日志分析

    最近在学习JVM和GC调优,今天总结下CMS的一些特点和要点,让我们先简单的看下整个堆年轻代和年老代的垃圾收集器组合(以下配合java8完美支持,其他版本可能稍有不同),其中标红线的则是我们今天要着重 ...

  2. 理解Java GC日志

    idea 在vm options处加入-XX:+PrintGCDetails,可打印GC日志. public class ReferenceCountingGC { public Object ins ...

  3. JVM小册(1)------jstat和Parallel GC日志

    JVM小册(1)------jstat和Parallel GC日志 一. 背景 在生产环境中,有时候会遇到OOM的情况,抛开Arthas 等比较成熟的工具以外,我们可以使用java 提供的jatat和 ...

  4. 一次CMS GC问题排查过程(理解原理+读懂GC日志)

    这个是之前处理过的一个线上问题,处理过程断断续续,经历了两周多的时间,中间各种尝试,总结如下.这篇文章分三部分: 1.问题的场景和处理过程:2.GC的一些理论东西:3.看懂GC的日志 先说一下问题吧 ...

  5. [转]一次CMS GC问题排查过程(理解原理+读懂GC日志)

    这个是之前处理过的一个线上问题,处理过程断断续续,经历了两周多的时间,中间各种尝试,总结如下.这篇文章分三部分: 1.问题的场景和处理过程:2.GC的一些理论东西:3.看懂GC的日志 先说一下问题吧 ...

  6. 【转】深入理解Major GC, Full GC, CMS

    声明:本文转自http://blog.csdn.net/iter_zc/article/details/41825395,转载务必声明. 很多人都分不清Major GC, Full GC的概念,事实上 ...

  7. 深入理解Major GC, Full GC, CMS

    很多人都分不清Major GC, Full GC的概念,事实上我查了下资料,也没有查到非常精确的Major GC和Full GC的概念定义.分不清这两个概念可能就会对这个问题疑惑:Full GC会引起 ...

  8. GC垃圾回收 | 深入理解G1垃圾收集器和GC日志

    来源:并发编程网链接:http://ifeve.com/深入理解G1垃圾收集器/ G1 GC是Jdk7的新特性之一.Jdk7+版本都可以自主配置G1作为JVM GC选项:作为JVM GC算法的一次重大 ...

  9. 【转载】Java垃圾回收内存清理相关(虚拟机书第三章),GC日志的理解,CPU时间、墙钟时间的介绍

    主要看<深入理解Java虚拟机> 第三张 P84 开始是垃圾收集相关. 1. 1960年诞生于MIT的Lisp是第一门采用垃圾回收的语言. 2. 程序计数器.虚拟机栈.本地方法栈3个区域随 ...

随机推荐

  1. Tensorflow.nn 核心模块详解

    看过前面的例子,会发现实现深度神经网络需要使用 tensorflow.nn 这个核心模块.我们通过源码来一探究竟. # Copyright 2015 Google Inc. All Rights Re ...

  2. 洛谷 P4999(数位DP)

    ###洛谷 P4999 题目链接 ### 题目大意:给你一个区间,求这段区间中所有数的,数位上的,数字之和. 分析: 这题与 洛谷 P2602 相似,稍微改一下就可以了. 求出 0 ~ 9 的个数,然 ...

  3. Protractor-引入Cucumber

    上一篇博文中我们已经在package.json中写入了cucumber依赖库,在执行 npm install 之后,cucumber就已经下载好了.接下来要做的是修改conf.js,请参考下图: 去年 ...

  4. 搭建Jupyter学习环境

    `python notebook`是一个基于浏览器的python数据分析工具,使用起来非常方便,具有极强的交互方式和富文本的展示效果.jupyter是它的升级版,它的安装也非常方便,一般`Anacon ...

  5. Java入门系列之字符串创建方式、判断相等(一)

    前言 陆续从0开始学习Java出于多掌握一门语言以后的路也会更宽,.NET和Java兼顾,虽然路还很艰难,但事在人为.由于Java和C#语法相似,所以关于一些很基础的内容不会再重头讲,Java系列中所 ...

  6. JQuery学习笔记(4)——ajax

    AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML) 原生 例子 点击按钮,访问服务器上的ajax_info.txt文件,获得txt ...

  7. vue-父子组件和ref

    父组件向子组件传值 <div id="app"> <!-- 父组件,可以在引用子组件的时候, 通过 属性绑定(v-bind:) 的形式, 把 需要传递给 子组件的 ...

  8. python BeautifulSoup 爬虫运行出现 exited with code -1073741571

    首先,exited with code -1073741571意思是栈溢出.具体可以看https://blog.csdn.net/vblittleboy/article/details/6613815 ...

  9. centos 安装gitlab

    1.开始安装依赖软件:yum -y install policycoreutils openssh-server openssh-clients postfix 2.设置postfix开机自启动,po ...

  10. Springboot vue 前后分离 跨域 Activiti6 工作流 集成代码生成器 shiro权限

    官网:www.fhadmin.org 特别注意: Springboot 工作流  前后分离 + 跨域 版本 (权限控制到菜单和按钮) 后台框架:springboot2.1.2+ activiti6.0 ...