JVM系列4-GC log

1、JVM参数

-XX:+PrintGC

参数-XX:+PrintGC开启了简单GC日志模式,为每一次新生代(young generation)的GC和每一次的Full GC打印一行信息。日志默认输出到终端。下面举例说明:

[GC 1843K->632K(116736K), 0.0112928 secs]
[Full GC 632K->558K(116736K), 0.0157125 secs]

每行开始首先是GC的类型(可以是“GC”或者“Full GC”),然后是在GC之前和GC之后已使用的堆空间,再然后是当前的堆容量,最后是GC持续的时间(以秒计)。

第一行的意思就是GC将已使用的堆空间从1843K减少到632K,当前的堆容量(译者注:GC发生时)是116736K,GC持续的时间是0.0112928 秒。

简单模式的GC日志格式是与GC算法无关的,日志也没有提供太多的信息。在上面的例子中,我们甚至无法从日志中判断是否GC将一些对象从young generation移到了old generation。所以详细模式的GC日志更有用一些。

-XX:PrintGCDetails

如果不是使用-XX:+PrintGC,而是-XX:PrintGCDetails,就开启了详细GC日志模式。在这种模式下,日志格式和所使用的GC算法有关。我们首先看一下使用PS垃圾收集器在young generation中生成的日志。为了便于阅读这里将一行日志分为多行并使用缩进。备注:共同使用PrintGC和PrintGCDetails,只会显示PrintGCDetails。

[GC
 [PSYoungGen: 1843K->632K(35840K)] 1843K->632K(116736K), 0.0149062 secs] 
 [Times: user=0.03 sys=0.00, real=0.02 secs] 

[Full GC 
 [PSYoungGen: 632K->0K(35840K)] 
 [ParOldGen: 0K->558K(80896K)] 632K->558K(116736K) 
 [PSPermGen: 2930K->2930K(21504K)], 0.0220857 secs]
 [Times: user=0.02 sys=0.00, real=0.02 secs] 

我们可以很容易发现:这是一次在young generation中的GC,它将已使用的堆空间从1843K减少到了632K,用时0.0149062秒。此外我们还可以得到更多的信息:所使用的垃圾收集器(即Parallel Scavenge GC)、young generation的大小和使用情况(在这个例子中PS垃圾收集器将young generation所使用的堆空间从1843K减少到632K)。

既然我们已经知道了young generation的大小,所以很容易判定发生了GC,因为young generation无法分配更多的对象空间:已经使用了35840K中的1843K。我们可以进一步得出结论,多数从young generation移除的对象仍然在堆空间中,只是被移到了old generation。

JVM系列4-GC log_第1张图片

接下来看一下Full GC的输出日志:
除了关于young generation的详细信息,日志也提供了old generation和permanent generation的详细信息。对于这三个generations,一样也可以看到所使用的垃圾收集器、堆空间的大小、GC前后的堆使用情况。需要注意的是显示堆空间的大小等于young generation和old generation各自堆空间的和。以上面为例,堆空间总共占用了558K,其中0K在young generation,558K在old generation。Full GC持续了0.02秒,用户空间的CPU执行时间为0.02秒,说明GC使用了多线程。

对不同generation详细的日志可以让我们分析GC的原因,如果某个generation的日志显示在GC之前,堆空间几乎被占满,那么很有可能就是这个generation触发了GC。但是在上面的例子中,三个generation中的任何一个都不是这样的,在这种情况下是什么原因触发了GC呢。对于Throughput垃圾收集器,在某一个generation被过度使用之前,GC ergonomics决定要启动GC。

Full GC也可以通过显式的请求而触发,可以是通过应用程序,或者是一个外部的JVM接口。这样触发的GC可以很容易在日志里分辨出来,因为输出的日志是以“Full GC(System)”开头的,而不是“Full GC”。

对于Serial垃圾收集器,详细的GC日志和Throughput垃圾收集器是非常相似的。唯一的区别是不同的generation日志可能使用了不同的GC算法。使用垃圾收集器作为一行日志的开头可以方便我们从日志就判断出JVM的GC设置。

对于CMS垃圾收集器,young generation的详细日志也和Throughput垃圾收集器非常相似,但是old generation的日志却不是这样。对于CMS垃圾收集器,在old generation中的GC是在不同的时间片内与应用程序同时运行的。GC日志自然也和Full GC的日志不同。而且在不同时间片的日志夹杂着在此期间young generation的GC日志。但是了解了上面介绍的GC日志的基本元素,也不难理解在不同时间片内的日志。只是在解释GC运行时间时要特别注意,由于大多数时间片内的GC都是和应用程序同时运行的,所以和那种独占式的GC相比,GC的持续时间更长一些并不说明一定有问题。

即使CMS垃圾收集器没有完成一个CMS周期,Full GC也可能会发生。如果发生了GC,在日志中会包含触发Full GC的原因,例如众所周知的”concurrent mode failure“。

JVM系列4-GC log_第2张图片

-XX:+PrintGCTimeStamps和-XX:+PrintGCDateStamps

使用-XX:+PrintGCTimeStamps可以将时间和日期也加到GC日志中。表示自JVM启动至今的时间戳会被添加到每一行中。例子如下:

0.148: [GC
 [PSYoungGen: 1843K->632K(35840K)] 1843K->632K(116736K), 0.0147505 secs]
 [Times: user=0.05 sys=0.00, real=0.01 secs] 
0.163: [Full GC
 [PSYoungGen: 632K->0K(35840K)]
 [ParOldGen: 0K->557K(80896K)] 632K->557K(116736K)
 [PSPermGen: 2844K->2843K(21504K)], 0.0149489 secs]
 [Times: user=0.02 sys=0.00, real=0.02 secs] 

如果同时指定了-XX:+PrintGCDateStamps,每一行就添加上了绝对的日期和时间:

2017-02-01T19:43:51.405+0800: 0.111: [GC
 [PSYoungGen: 1843K->632K(35840K)] 1843K->632K(116736K), 0.0019876 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 

2017-02-01T19:43:51.407+0800: 0.113: [Full GC
 [PSYoungGen: 632K->0K(35840K)]
 [ParOldGen: 0K->557K(80896K)] 632K->557K(116736K)
 [PSPermGen: 2844K->2843K(21504K)], 0.0378132 secs]
 [Times: user=0.08 sys=0.00, real=0.04 secs] 

-Xloggc

缺省的GC日志时输出到终端的,使用-Xloggc:也可以输出到指定的文件。需要注意这个参数隐式的设置了参数-XX:+PrintGC和-XX:+PrintGCTimeStamps,但为了以防在新版本的JVM中有任何变化,我仍建议显示的设置这些参数。

2、可管理的JVM参数

一个常常被讨论的问题是在生产环境中GC日志是否应该开启。因为它所产生的开销通常都非常有限,因此我的答案是需要开启。但并不一定在启动JVM时就必须指定GC日志参数。

HotSpot JVM有一类特别的参数叫做可管理的参数。对于这些参数,可以在运行时修改他们的值。我们这里所讨论的所有参数以及以“PrintGC”开头的参数都是可管理的参数。这样在任何时候我们都可以开启或是关闭GC日志。比如我们可以使用JDK自带的jinfo工具来设置这些参数,或者是通过JMX客户端调用HotSpotDiagnostic MXBean的setVMOption方法来设置这些参数。

3、GC日志分析工具 - GCviewer

4、CMS日志分析

4.1 JVM参数设置

-XX:+PrintGC            // 输出GC日志
-XX:+PrintGCDetails     // 输出GC的详细日志
-XX:+PrintGCTimeStamps  // 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps  // 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-Xloggc:./log/gc.log    // 日志文件的输出路径

-XX:+UseParNewGC        // 新生代使用ParNew回收器
-XX:+UseConcMarkSweepGC // 老年代使用CMS回收器

4.2 ParNew and CMS

年轻代:采用 stop-the-world mark-copy:复制算法 算法;
老年代:采用 Mostly Concurrent mark-sweep:标记清除 算法;
设计目标:年老代收集的时候避免长时间的暂停;

能够达成该目标主要因为以下两个原因:
1. 它不会花时间整理压缩年老代,而是维护了一个叫做 free-lists 的数据结构,该数据结构用来管理那些回收再利用的内存空间;
2. mark-sweep分为多个阶段,其中一大部分阶段GC的工作是和Application threads的工作同时进行的(当然,gc线程会和用户线程竞争CPU的时间),默认的GC的工作线程为你服务器物理CPU核数的1/4;

备注:当你的服务器是多核同时你的目标是低延时,那该GC的搭配则是你的不二选择。

4.3 日志形式

2016-08-23T02: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]
2016-08-23T02:23:07.321-0200: 64.425: [GC (CMS Initial Mark) [1 CMS-initial-mark: 10812086K(11901376K)] 10887844K(12514816K), 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-08-23T02:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
2016-08-23T02: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]
2016-08-23T02:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]
2016-08-23T02: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]
2016-08-23T02:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]
2016-08-23T02: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]
2016-08-23T02: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]
2016-08-23T02:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]
2016-08-23T02: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]
2016-08-23T02:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]
2016-08-23T02: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]

4.4 Minor GC

从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。这一定义既清晰又易于理解。但是,当发生Minor GC事件的时候,有一些有趣的地方需要注意到:

  • 当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。
  • 内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。
  • 执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。
  • 质疑常规的认知,所有的 Minor GC 都会触发“stop-the-world”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。
    所以 Minor GC 的情况就相当清楚了 — 每次 Minor GC 会清理年轻代的内存

2016-08-23T02:23:07.219-0200*1: 64.3222*:
[GC*3(Allocation Failure4*) 64.322:
  [ParNew*5: 613404K->68068K6(613440K)7, 0.1020465 secs8*]
  10885349K->10880154K*9(12514816K)10, 0.1021309 secs11*]
 [Times: user=0.78 sys=0.01, real=0.11 secs]12

  1. 2016-08-23T02:23:07.219-0200 – GC发生的时间;
  2. 64.322 – GC开始,相对JVM启动的相对时间,单位是秒;
  3. GC – 区别MinorGC和FullGC的标识,这次代表的是MinorGC;
  4. Allocation Failure – MinorGC的原因,在这个case里边,由于年轻代不满足申请的空间,因此触发了MinorGC;
  5. ParNew – 收集器的名称,它预示了年轻代使用一个并行的 mark-copy stop-the-world 垃圾收集器;
  6. 613404K->68068K – 收集前后年轻代的使用情况;
  7. (613440K) – 整个年轻代的容量;
  8. 0.1020465 secs – 这个解释用原滋原味的解释:Duration for the collection w/o final cleanup.
  9. 10885349K->10880154K – 收集前后整个堆的使用情况;
  10. (12514816K) – 整个堆的容量;
  11. 0.1021309 secs – ParNew收集器标记和复制年轻代活着的对象所花费的时间(包括和老年代通信的开销、对象晋升到老年代时间、垃圾收集周期结束一些最后的清理对象等的花销);
  12. [Times: user=0.78 sys=0.01, real=0.11 secs] – GC事件在不同维度的耗时,具体的用英文解释起来更加合理:
    user – Total CPU time that was consumed by Garbage Collector threads during this collection
    sys – Time spent in OS calls or waiting for system event
    real – Clock time for which your application was stopped. With Parallel GC this number should be close to (user time + system time) divided by the number of threads used by the Garbage Collector. In this particular case 8 threads were used. Note that due to some activities not being parallelizable, it always exceeds the ratio by a certain amount.

我们来分析下对象晋升问题(原文中的计算方式有问题):
开始的时候:整个堆的大小是 10885349K,年轻代大小是613404K,这说明老年代大小是 10885349-613404=10271945K,
收集完成之后:整个堆的大小是 10880154K,年轻代大小是68068K,这说明老年代大小是 10880154-68068=10812086K,
老年代的大小增加了:10812086-10271945=608209K,也就是说 年轻代到年老代promot了608209K的数据;

图形分析
JVM系列4-GC log_第3张图片

4.5 Full/Major GC

大家应该注意到,目前,这些术语无论是在 JVM 规范还是在垃圾收集研究论文中都没有正式的定义。但是我们一看就知道这些在我们已经知道的基础之上做出的定义是正确的,Minor GC 清理年轻带内存应该被设计得简单:

  • Major GC 是清理老年代。
  • Full GC 是清理整个堆空间—包括年轻代和老年代。

很不幸,实际上它还有点复杂且令人困惑。首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。

这使得我们不用去关心到底是叫 Major GC 还是 Full GC,大家应该关注当前的 GC 是否停止了所有应用程序的线程,还是能够并发的处理而不用停掉应用程序的线程

2016-08-23T11:23:07.321-0200: 64.425: [GC (CMS Initial Mark)1
[1 CMS-initial-mark: 10812086K(11901376K)] 10887844K(12514816K), 0.0001997 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]

2016-08-23T11:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
2016-08-23T11:23:07.357-0200: 64.460: [CMS-concurrent-mark2: 0.035/0.035 secs]
[Times: user=0.07 sys=0.00, real=0.03 secs]

2016-08-23T11:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]
2016-08-23T11:23:07.373-0200: 64.476: [CMS-concurrent-preclean3: 0.016/0.016 secs]
[Times: user=0.02 sys=0.00, real=0.02 secs]

2016-08-23T11:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]
2016-08-23T11:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean4: 0.167/1.074 secs] [
Times: user=0.20 sys=0.00, real=1.07 secs]

2016-08-23T11:23:08.447-0200: 65.550: [GC (CMS Final Remark5)
[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]

2016-08-23T11:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]
2016-08-23T11:23:08.485-0200: 65.588: [CMS-concurrent-sweep6: 0.027/0.027 secs]
[Times: user=0.03 sys=0.00, real=0.03 secs]

2016-08-23T11:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]
2016-08-23T11:23:08.497-0200: 65.601: [CMS-concurrent-reset7: 0.012/0.012 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

从上面的日志中可以看到,这和上节(GC算法 垃圾收集器概述)中介绍的CMS垃圾回收器的工作步骤是一致的,如下图,
JVM系列4-GC log_第4张图片

从上图可以看出,CMS回收时有两个阶段会发生“STW”,分别是:初始标记和重新标记

Phase 1: Initial Mark
这是CMS中两次stop-the-world事件中的一次。它有两个目标:一是标记老年代中所有的GC Roots;二是标记被年轻代中活着的对象引用的对象。
标记结果如下:
JVM系列4-GC log_第5张图片

分析:
2016-08-23T11:23:07.321-0200: 64.42**1: [GC (**CMS Initial Mark2
[1 CMS-initial-mark: 10812086K**3(11901376K)4] 10887844K5(12514816K)6**, 0.0001997 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]7

  1. 016-08-23T11:23:07.321-0200: 64.42 – GC事件开始,包括时钟时间和相对JVM启动时候的相对时间,下边所有的阶段改时间的含义相同;
  2. CMS Initial Mark – 收集阶段,开始收集所有的GC Roots和直接引用到的对象;
  3. 10812086K – 当前老年代使用情况;
  4. (11901376K) – 老年代可用容量;
  5. 10887844K – 当前整个堆的使用情况;
  6. (12514816K) – 整个堆的容量;
  7. 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] – 时间计量;

Phase 2: Concurrent Mark
这个阶段会遍历整个老年代并且标记所有存活的对象,从“初始化标记”阶段找到的GC Roots开始。并发标记的特点是和应用程序线程同时运行,不会发生STW。并不是老年代的所有存活对象都会被标记,因为标记的同时应用程序会改变一些对象的引用等。

标记结果如下:

JVM系列4-GC log_第6张图片
在上边的图中,一个引用的箭头已经远离了当前对象(current obj)

分析:
2016-08-23T11:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
2016-08-23T11:23:07.357-0200: 64.460: [CMS-concurrent-mark1: 035/0.035 secs**2] [Times: user=0.07 sys=0.00, real=0.03 secs]2**
1. CMS-concurrent-mark – 并发收集阶段,这个阶段会遍历整个年老代并且标记活着的对象;
2. 035/0.035 secs – 展示该阶段持续的时间和时钟时间;
3. [Times: user=0.07 sys=0.00, real=0.03 secs] – 同上

Phase 3: Concurrent Preclean
这个阶段又是一个并发阶段,和应用线程并行运行,不会中断他们,不会发生STW。前一个阶段在并行运行的时候,一些对象的引用已经发生了变化,当这些引用发生变化的时候,JVM会标记堆的这个区域为Dirty Card(包含被标记但是改变了的对象,被认为”dirty”),这就是 Card Marking。
如下图

JVM系列4-GC log_第7张图片
在pre-clean阶段,那些能够从dirty card对象到达的对象也会被标记,这个标记做完之后,dirty card标记就会被清除了,如下:

JVM系列4-GC log_第8张图片

分析:
2016-08-23T11:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]
2016-08-23T11:23:07.373-0200: 64.476: [CMS-concurrent-preclean1: 0.016/0.016 secs**2**]
[Times: user=0.02 sys=0.00, real=0.02 secs]**3
1. CMS-concurrent-preclean – 这个阶段负责前一个阶段标记了又发生改变的对象标记;
2. 0.016/0.016 secs – 展示该阶段持续的时间和时钟时间;
3. [Times: user=0.02 sys=0.00, real=0.02 secs] – 同上

Phase 4: Concurrent Abortable Preclean
又一个并发阶段不会停止应用程序线程。这个阶段尝试着去承担STW的Final Remark阶段足够多的工作。这个阶段持续的时间依赖好多的因素,由于这个阶段是重复的做相同的事情直到发生aboart的条件(比如:重复的次数、多少量的工作、持续的时间等等)之一才会停止。

2016-08-23T11:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]2016-08-23T11:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean1: 0.167/1.074 secs**2**]
[Times: user=0.20 sys=0.00, real=1.07 secs]3

  1. CMS-concurrent-abortable-preclean – 可终止的并发预清理;
  2. 0.167/1.074 secs – 展示该阶段持续的时间和时钟时间(It is interesting to note that the user time reported is a lot smaller than clock time. Usually we have seen that real time is less than user time, meaning that some work was done in parallel and so elapsed clock time is less than used CPU time. Here we have a little amount of work – for 0.167 seconds of CPU time, and garbage collector threads were doing a lot of waiting. Essentially, they were trying to stave off for as long as possible before having to do an STW pause. By default, this phase may last for up to 5 seconds);
  3. [Times: user=0.20 sys=0.00, real=1.07 secs] – 同上

这个阶段很大程度的影响着即将来临的Final Remark的停顿,有相当一部分重要的 configuration options 和 失败的模式;

Phase 5: Final Remark
这个阶段是CMS中第二个并且是最后一个STW的阶段。该阶段的任务是完成标记整个年老代的所有的存活对象。由于之前的预处理是并发的,它可能跟不上应用程序改变的速度,这个时候,STW是非常需要的来完成这个严酷考验的阶段。

通常CMS尽量运行Final Remark阶段在年轻代是足够干净的时候,目的是消除紧接着的连续的几个STW阶段。

分析:
2016-08-23T11:23:08.447-0200: 65.550**1: [GC (CMS Final Remark2) [YG occupancy: 387920 K (613440 K)3]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)8] 11200006K(12514816K) 9, 0.0110730 secs10] [[Times: user=0.06 sys=0.00, real=0.01 secs]11**

  1. 2016-08-23T11:23:08.447-0200: 65.550 – 同上;
  2. CMS Final Remark – 收集阶段,这个阶段会标记老年代全部的存活对象,包括那些在并发标记阶段更改的或者新创建的引用对象;
  3. YG occupancy: 387920 K (613440 K) – 年轻代当前占用情况和容量;
  4. [Rescan (parallel) , 0.0085125 secs] – 这个阶段在应用停止的阶段完成存活对象的标记工作;
  5. weak refs processing, 0.0000243 secs]65.559 – 第一个子阶段,随着这个阶段的进行处理弱引用;
  6. lass unloading, 0.0013120 secs]65.560 – 第二个子阶段(that is unloading the unused classes, with the duration and timestamp of the phase);
  7. scrub string table, 0.0001759 secs – 最后一个子阶段(that is cleaning up symbol and string tables which hold class-level metadata and internalized string respectively)
  8. 10812086K(11901376K) – 在这个阶段之后老年代占有的内存大小和老年代的容量;
  9. 11200006K(12514816K) – 在这个阶段之后整个堆的内存大小和整个堆的容量;
  10. 0.0110730 secs – 这个阶段的持续时间;
  11. [Times: user=0.06 sys=0.00, real=0.01 secs] – 同上;

通过以上5个阶段的标记,老年代所有存活的对象已经被标记并且现在要通过Garbage Collector采用清扫的方式回收那些不能用的对象了。

Phase 6: Concurrent Sweep
和应用线程同时进行,不需要STW。这个阶段的目的就是移除那些不用的对象,回收他们占用的空间并且为将来使用。
如图:

JVM系列4-GC log_第9张图片

分析:
2016-08-23T11:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start] 2016-08-23T11:23:08.485-0200: 65.588: [CMS-concurrent-sweep1: 0.027/0.027 secs**2**]
[[Times: user=0.03 sys=0.00, real=0.03 secs] 3

  1. CMS-concurrent-sweep – 这个阶段主要是清除那些没有标记的对象并且回收空间;
  2. 0.027/0.027 secs – 展示该阶段持续的时间和时钟时间;
  3. [Times: user=0.03 sys=0.00, real=0.03 secs] – 同上

Phase 7: Concurrent Reset
这个阶段并发执行,重新设置CMS算法内部的数据结构,准备下一个CMS生命周期的使用。
2016-08-23T11:23:08.485-0200: 65.589: [CMS-concurrent-reset-start] 2016-08-23T11:23:08.497-0200: 65.601: [CMS-concurrent-reset1: 0.012/0.012 secs**2] [[Times: user=0.01 sys=0.00, real=0.01 secs]3**

  1. CMS-concurrent-reset – 这个阶段重新设置CMS算法内部的数据结构,为下一个收集阶段做准备;
  2. 0.012/0.012 secs – 展示该阶段持续的时间和时钟时间;
  3. [Times: user=0.01 sys=0.00, real=0.01 secs] – 同上

5、G1日志分析

http://www.cnblogs.com/kaiblog/p/4827073.html
http://zhaoyanblog.com/archives/441.html
https://blogs.oracle.com/poonam/entry/understanding_g1_gc_logs

你可能感兴趣的:(JVM)