GC日志分析-JVM

548 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

  • 前阵子遇到JVM内存溢出问题,跟流式处理有一定的关系,解决分析篇在后续文章会更新。前天科三路考,同事反馈海外站点主机内存告警,我还未回,同事便通过重启暂时解决。 之前分析过,无内存泄漏问题,这种情况下分析gc日志是可以看到内存变化的,但已覆盖原gc日志,在此记录GC日志分析博客。

  • 本博客涵盖 G1垃圾收集器及ParNew + Serial Old垃圾收集器GC日志分析

  • 目的

      1. 主机应用因jvm内存挂断,可通过gc日志分析是不是堆内存无法及时回收
      2. 后期再次重启,会让同事先将gc日志先额外保存,方便后期分析
      3. 通过gc日志帮助jVM调优    
    

GC配置参数

参数解释
-XX:+PrintGC打印简单GC日志。 类似:-verbose:gc
-XX:+PrintGCDetails打印GC详细信息
-XX:+PrintGCTimeStamps输出GC的时间戳(以基准时间的形式),本人亲试JDK11版本好像不支持
-XX:+PrintGCDateStamps输出GC的时间戳(以日期的形式),本人亲试JDK11版本好像不支持
-XX:+PrintHeapAtGC在进行GC的前后打印出堆的信息
-Xloggc:/opt/logs/gc.log指定输出路径收集日志到日志文件

例:

java -Xms20m -Xmx20m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:D:/logs/gc.log -jar GCTest.jar  

备注: -XX:+PrintGCDateStamps/-XX:+PrintGCTimeStamps 适用JDK1.8, 注意JDK版本 ,JDK11不支持,报错如下:

Unrecognized VM option 'PrintGCDateStamps

GC日志分析

本地代码:

package com.yecun;

import java.util.ArrayList;

public class GCTest {

    public static void main(String[] args) throws InterruptedException {
        // 可回收
        HelloWorld qingfeng = new HelloWorld();
        System.out.println(qingfeng.getClass().getName());
        // list 不可回收
        ArrayList<Object> list = new ArrayList<>();
        HelloWorld helloWorld = null;
        while(true){
            helloWorld = new HelloWorld();
            list.add(helloWorld);
        }
    }
}

G1回收器GC日志分析(JDK11)

idea的jvm配置参数

-XX:+UseG1GC -Xms20m -Xmx20m -XX:NewRatio=2 -XX:+PrintGCDetails -Xloggc:D:\java_workspace\logs\gc.log

gc.log内容

[0.032s][info][gc,heap] Heap region size: 1M
[0.033s][info][gc     ] Using G1
[0.033s][info][gc,heap,coops] Heap address: 0x00000000fec00000, size: 20 MB, Compressed Oops mode: 32-bit
// 第1次回收
[0.414s][info][gc,start     ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
[0.414s][info][gc,task      ] GC(0) Using 2 workers of 10 for evacuation
[0.418s][info][gc,phases    ] GC(0)   Pre Evacuate Collection Set: 0.0ms
[0.418s][info][gc,phases    ] GC(0)   Evacuate Collection Set: 3.0ms
[0.418s][info][gc,phases    ] GC(0)   Post Evacuate Collection Set: 0.3ms
[0.418s][info][gc,phases    ] GC(0)   Other: 0.4ms
[0.418s][info][gc,heap      ] GC(0) Eden regions: 6->0(5)
[0.418s][info][gc,heap      ] GC(0) Survivor regions: 0->1(1)
[0.418s][info][gc,heap      ] GC(0) Old regions: 0->1
[0.418s][info][gc,heap      ] GC(0) Humongous regions: 0->0
[0.418s][info][gc,metaspace ] GC(0) Metaspace: 5549K->5549K(1056768K)
[0.418s][info][gc           ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 6M->1M(20M) 4.443ms
[0.418s][info][gc,cpu       ] GC(0) User=0.00s Sys=0.00s Real=0.00s
// 第2次回收
[0.766s][info][gc,start     ] GC(1) Pause Young (Normal) (G1 Evacuation Pause)
[0.766s][info][gc,task      ] GC(1) Using 2 workers of 10 for evacuation
[0.770s][info][gc,phases    ] GC(1)   Pre Evacuate Collection Set: 0.0ms
[0.770s][info][gc,phases    ] GC(1)   Evacuate Collection Set: 3.6ms
[0.770s][info][gc,phases    ] GC(1)   Post Evacuate Collection Set: 0.2ms
[0.770s][info][gc,phases    ] GC(1)   Other: 0.2ms
[0.770s][info][gc,heap      ] GC(1) Eden regions: 5->0(5)
[0.770s][info][gc,heap      ] GC(1) Survivor regions: 1->1(1)
[0.770s][info][gc,heap      ] GC(1) Old regions: 1->2
[0.770s][info][gc,heap      ] GC(1) Humongous regions: 0->0
[0.770s][info][gc,metaspace ] GC(1) Metaspace: 5550K->5550K(1056768K)
[0.770s][info][gc           ] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 6M->2M(20M) 4.305ms
[0.770s][info][gc,cpu       ] GC(1) User=0.03s Sys=0.00s Real=0.00s

以上是第一和第二次的GC日志

日志解析

  • [0.414s][info][gc,start ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) -------GC回收开始,暂停年轻代(只针对年轻代进行回收)
  • [0.414s][info][gc,task ] GC(0) Using 2 workers of 10 for evacuation -------开启2个工作线程疏散
  • YOUNG GC 标记阶段
  [info][gc,phases    ] GC(0)   Pre Evacuate Collection Set: 0.0ms
  [info][gc,phases    ] GC(0)   Evacuate Collection Set: 3.7ms
  [info][gc,phases    ] GC(0)   Post Evacuate Collection Set: 0.3ms
  • YOUNG GC 执行回收阶段
 [info][gc,heap      ] GC(0) Eden regions: 6->0(5)   ---- eden区回前6M,回收后0M,总共5M(至于为何超出5M,这个目前我不大清楚,如有知道的兄弟,评论区请留言)
 [info][gc,heap      ] GC(0) Survivor regions: 0->1(1) ---- survivor gc前0M,gc后1M
 [info][gc,heap      ] GC(0) Old regions: 0->1  ---- 老年代回收前0M,回收后1M
 [info][gc,heap      ] GC(0) Humongous regions: 0->0 ---- 大型区域(存储大对象区)
 [info][gc,metaspace ] GC(0) Metaspace: 5543K->5543K(1056768K) ---- 元空间
 [0.418s][info][gc   ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 6M->1M(20M) 4.443ms 
 ---- 本次回收前,堆空间6M,回收后1M(survivor区+old区可能不超出2M吧),总共20M
 [0.418s][info][gc,cpu       ] GC(0) User=0.00s Sys=0.00s Real=0.00s ---- 本次回收 User:用户态消耗时间  Sys:内核耗时  Real:实际消耗时间

ParNew + Serial Old 的组合进行内存回收

idea的jvm配置参数

-Xms20m -Xmx20m -XX:+UseParNewGC -XX:NewRatio=2 -XX:+PrintGCDetails -Xloggc:D:\java_workspace\logs\gc.log

gc.log内容

[GC (Allocation Failure) [ParNew: 6146K->753K(9216K), 0.0065877 secs] 6146K->4849K(19456K),

0.0092108 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]

Heap

par new generation total 9216K, used 7220K [0x00000000fec00000, 0x00000000ff600000,

0x00000000ff600000)

eden space 8192K, 78% used [0x00000000fec00000, 0x00000000ff250ea0, 0x00000000ff400000)

from space 1024K, 73% used [0x00000000ff500000, 0x00000000ff5bc488, 0x00000000ff600000)

to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)

tenured generation total 10240K, used 4096K [0x00000000ff600000, 0x0000000100000000,

0x0000000100000000)

the space 10240K, 40% used [0x00000000ff600000, 0x00000000ffa00020, 0x00000000ffa00200,

0x0000000100000000)

Metaspace used 3362K, capacity 4496K, committed 4864K, reserved 1056768K

class space used 369K, capacity 388K, committed 512K, reserved 1048576K

Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old

collector is deprecated and will likely be removed in a future release

日志解析

例:

[GC (Allocation Failure)[ParNew: 6146K->753K(9216K), 0.0065877 secs] 6146K->4849K(19456K),

0.0092108 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 

解读前,先将上述日志抽象

[a(b)[c:d->e(f), g secs] h->i(j), k secs] [Times: user:l sys=m, real=n secs]

附上含义:

  • a: GC 或者是 Full GC

  • b: 用来说明发生这次 GC 的原因,内存分配失败,EDEN区再分配会占满

  • c: 表示发生GC的区域,这里表示是新生代发生了GC,上面那个例子是因为在新生代中内存不够给新对象分配了,然后触发了 GC

  • d: GC 之前该区域已使用的容量

  • e: GC 之后该区域已使用的容量

  • f: 该内存区域的总容量

  • g: 表示该区域这次 GC 使用的时间

  • h: 表示 GC 前整个堆的已使用容量

  • i: 表示 GC 后整个堆的已使用容量

  • j: 表示 Java 堆的总容量

  • k: 表示 Java堆 这次 GC 使用的时间

  • l: 代表用户态消耗的 CPU 时间

  • m: 代表内核态消耗的 CPU 时间

  • n: 整个 GC 事件从开始到结束的墙钟时间(Wall Clock Time)

以上含义,对非G1垃圾回收器生成的日志做出解释,还是挺容易分辨的

GC日志分析工具

线上分析(网址:gceasy.io/)

  1. 分析工具gceasy 界面较为简单,可选择中文版本 1.png
  2. 选择gc文件后,点击分析,结果如截图 2.png

GCViewer

GCViewer是一个小工具,可视化展示生成的详细GC输出。

GCViewer下载

解压后目录如下: 截图1 1.png 截图2 2.png 启动命令

java -jar gcviewer-1.37-SNAPSHOT.jar

弹出界面 3.png 返回结果 4.png