jvm的GC日志分析

版权声明:本文为博主原创文章,欢迎转载,转载请附上原文地址。 https://blog.csdn.net/xxc1605629895/article/details/83833012

一、GC 类型

Java中的GC有哪几种类型?

参数 描述
UseSerialGC 虚拟机运行在Client模式的默认值,打开此开关参数后,使用Serial+Serial Old收集器组合进行垃圾收集
UseParNewGC 打开此开关参数后,使用ParNew+Serial Old收集器组合进行垃圾收集
UseConcMarkSweepGC 打开此开关参数后,使用ParNew+CMS+Serial Old收集器组合进行垃圾收集。Serial Old作为CMS收集器出现Concurrent Mode Failure的备用垃圾收集器
UseParallelGC 虚拟机运行在Server模式的默认值,打开此开关参数后,使用Parallel Scavenge+Serial Old收集器组合进行垃圾收集。
UseParallelOldGC 打开此开关参数后,使用Parallel Scavenge+Parallel Old收集器组合进行垃圾收集
UseG1GC 代开此开关参数后,使用G1垃圾收集器进行垃圾收集

在Java程序启动完成后,通过jps观察进程来查询到当前运行的java进程,使用

jinfo –flag UseSerialGC pid

的方式可以定位其使用的gc策略,因为这些参数都是boolean型的常量,如果使用该种gc策略会出现+号,否则-号。

使用-XX:+上述GC策略可以开启对应的GC策略。

二、 GC日志主要参数

JVM的GC日志的主要参数包括如下几个:

-XX:+PrintGC 输出GC日志

-XX:+PrintGCDetails 输出GC的详细日志

-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)

-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)

-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息

-XX:+PrintTenuringDistribution 输出显示在survivor空间里面有效的对象的岁数情况

-XX:+PrintGCApplicationStoppedTime // 输出GC造成应用暂停的时间

-Xloggc:../logs/gc.log 日志文件的输出路径 (可以使用绝对路径或者相对路径)

三、常用JVM参数

分析gc日志后,经常需要调整jvm内存相关参数,常用参数如下

-Xms:初始堆大小;默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,
 	  JVM就会增大堆直到-Xmx的最大限制 (对应 InitialHeapSize 设置)

-Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
	(对应 MaxHeapSize 设置)

-Xmn:新生代的内存空间大小,注意:此处的大小是(eden+ 2 survivor space)。与jmap -heap中显示的New gen是不同的。
	  整个堆大小=新生代大小 + 老生代大小 + 永久代大小。 
	  在保证堆大小不变的情况下,增大新生代后,将会减小老生代大小。
	  此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-XX: SurvivorRatio:新生代中Eden区域与Survivor区域的容量比值,默认值为8。
	 两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。

-Xss:每个线程的堆栈大小。JDK5.0 以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。
	  应根据应用的线程所需内存大小进行适当调整。在相同物理内存下,减小这个值能生成更多的线程。
	  一般小的应用, 如果栈不是很深, 应该是128k够用的,大的应用建议使用256k。
	  (对应 ThreadStackSize 设置)。

-XX:PermSize:设置永久代(perm gen)初始值 (JDK 1.8 中已经不再支持)。

-XX:MaxPermSize:设置持久代最大值 (JDK 1.8 中已经不再支持)。

-XX:MetaspaceSize:设置元空间(Meta Space)初始值(JDK 1.8 中新增)

-XX:MaxMetaspaceSize: 设置元空间(Meta Space)的最大值(JDK 1.8 新增)

四、GC 日志实例

1、 Serial GC 日志实例

测试代码如下:

import java.util.ArrayList;
import java.util.List;

public class HeapOOM {

	static class OOMObject {
		byte[] b = new byte[1024 * 1024];
	}

	public static void main(String[] args) {
		int count = 0;
		List<OOMObject> list = new ArrayList<OOMObject>();
		try {
			while(count < 20) {
				list.add(new OOMObject());
				Thread.sleep(200);
				count++;
			}
		} catch (Throwable e) {
			e.printStackTrace();
		} finally {
			System.out.println("count: " + count);
		}
	}
	
}

JVM 参数:

-XX:+PrintGCDetails  -XX:+PrintGCTimeStamps -Xloggc:./gc.log -Xms20m -Xmx20m

运行结果如下:

count: 17
java.lang.OutOfMemoryError: Java heap space
	at com.johnfnash.learn.jvm.mem.HeapOOM$OOMObject.<init>(HeapOOM.java:12)
	at com.johnfnash.learn.jvm.mem.HeapOOM.main(HeapOOM.java:20)

查看 gc 日志:

Java HotSpot(TM) Client VM (25.171-b11) for windows-x86 JRE (1.8.0_171-b11)
......

2.646: [GC (Allocation Failure) 2.646: [DefNew: 4830K->496K(6144K), 0.0119400 secs] 4830K->4592K(19840K), 0.0124441 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
3.743: [GC (Allocation Failure) 3.743: [DefNew: 5725K->0K(6144K), 0.0126833 secs] 9821K->9711K(19840K), 0.0129225 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
4.765: [GC (Allocation Failure) 4.765: [DefNew: 5221K->5221K(6144K), 0.0000545 secs]4.765: [Tenured: 9711K->12784K(13696K), 0.0214370 secs] 14933K->14832K(19840K), [Metaspace: 80K->80K(4480K)], 0.0227165 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
5.405: [Full GC (Allocation Failure) 5.405: [Tenured: 12784K->12784K(13696K), 0.0041136 secs] 18007K->17904K(19840K), [Metaspace: 80K->80K(4480K)], 0.0043785 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
5.410: [Full GC (Allocation Failure) 5.410: [Tenured: 12784K->12772K(13696K), 0.0095670 secs] 17904K->17892K(19840K), [Metaspace: 80K->80K(4480K)], 0.0097671 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 6144K, used 5320K [0x03e00000, 0x044a0000, 0x044a0000)
  eden space 5504K,  96% used [0x03e00000, 0x04332390, 0x04360000)
  from space 640K,   0% used [0x04360000, 0x04360000, 0x04400000)
  to   space 640K,   0% used [0x04400000, 0x04400000, 0x044a0000)
 tenured generation   total 13696K, used 12772K [0x044a0000, 0x05200000, 0x05200000)
   the space 13696K,  93% used [0x044a0000, 0x051191e8, 0x05119200, 0x05200000)
 Metaspace       used 81K, capacity 2244K, committed 2368K, reserved 4480K

最前面的数字 “2.646:” 和 “5.045:” 代表了 GC 发生时间,这个数字的含义是从 Java 虚拟机启动以来经过的秒数。

GC 日志开头的 “[GC” 和 “[Full GC” 说明了这次垃圾收集的停顿类型,而不是用来区分新生代GC还是老年代GC。如果有“Full”,说明这次GC是发生了 Stop-The-World 的,例如下面这段新生代收集器 Serial 的日志也会出现“[Full GC”(这一般是因为出现了分配担保失败之类的问题,所以才导致 STW)。这里的 “(Allocation Failure) ” 是引起垃圾回收的原因,本次GC是因为年轻代中没有任何合适的区域能够存放需要分配的数据而触发的。如果是调用 System.gc() 方法锁触发的收集,那么在这里将显示“[Full GC(System.gc())”。

5.405: [Full GC (Allocation Failure) 5.405: [Tenured: 12784K->12784K(13696K), 0.0041136 secs] 

接下来的“[DefNew”、“[Tenured”、“[Metaspace” 表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的,例如上面样例使用的 Serial 收集器中的新生代名为“Default New Generation”,所以显示的是“[DefNew”。如果是 ParNew 收集器,新生代名称就会变为“[ParNew”,意为“Parallel New Generation”。如果采用 Parallel Scavenge 收集器,那它配套的新生代成为“PSYoundGen”,老年代和永久代同理,名称也是有收集器决定的。
(注意,这里的 def new generation 只包含 eden + from survivor,整个新生代则包含 eden + from survivor + to survivor)

后面方括号内部的“4830K->496K(6144K)”含义是“GC前该区域已使用容量 -> GC 后该内存区域已使用容量(该内存区域总容量)”。而在方括号之外的“4830K->4592K(19840K)” 表示“GC 前 Java 堆已使用容量 -> GC 后 Java 堆已使用容量(Java 堆总容量)”。

再往后,“0.0124441 secs”表示该内存区域 GC 所占用的时间,单位是秒。有的收集器会给出更具体的时间数据,如“[Times : user=0.01 sys=0.00, real=0.02 secs]”,这里面的 user、sys 和 real 与Linus 的time命令所输出的时间含义一致,分别代表用户态消耗的CPU时间、内核态小号的 CPU 事件和操作从开始到结束所经过的墙钟时间(Wall Clock Time)。CPU时间与墙钟时间的区别是,墙钟时间包括各种非运算的等待耗时,例如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多 CPU 或者多核的话,多线程操作会叠加这些 CPU 时间,所以读者看到 user 或者 sys 时间超过 real 时间是完全正常的。

另外,

[0x03e00000, 0x044a0000, 0x044a0000)

这种形式的日志有两种意义:

当这种日志出现在generation的详细信息里的时候,三个数字在HotSpot里分别称为low_boundary、high、high_boundary。

low_boundary: reserved space的最低地址边界;通常也跟“low”相等,这是commited space的最低地址边界 

high: commited space的最高地址边界 

high_boundary: reserved space的最高地址边界。 

[low_boundary, high_boundary) 范围内的就是reserved space,这个space的大小就是max capacity。

[low, high)范围内的就是commited space,而这个space的大小就是current capacity(当前容量),简称capacity。

capacity有可能在一对最小值和最大值之间浮动。最大值就是上面说的max capacity。

-XX:+PrintTenuringDistribution 与 -XX:MaxTenuringThreshold

-XX:+PrintGCDetails
-XX:+PrintHeapAtGC
-XX:+PrintGCDateStamps
-XX:+PrintTenuringDistribution
-verbose:gc
-Xloggc:gc.lo

后可以看到进行GC前后的堆内存信息

{Heap before GC invocations=0 (full 0):
 def new generation   total 6144K, used 4830K [0x03800000, 0x03ea0000, 0x03ea0000)
  eden space 5504K,  87% used [0x03800000, 0x03cb79f0, 0x03d60000)
  from space 640K,   0% used [0x03d60000, 0x03d60000, 0x03e00000)
  to   space 640K,   0% used [0x03e00000, 0x03e00000, 0x03ea0000)
 tenured generation   total 13696K, used 0K [0x03ea0000, 0x04c00000, 0x04c00000)
   the space 13696K,   0% used [0x03ea0000, 0x03ea0000, 0x03ea0200, 0x04c00000)
 Metaspace       used 79K, capacity 2244K, committed 2368K, reserved 4480K
1.134: [GC (Allocation Failure) 1.145: [DefNew
Desired survivor size 327680 bytes, new threshold 1 (max 15)
- age   1:     508840 bytes,     508840 total
: 4830K->496K(6144K), 0.0145337 secs] 4830K->4592K(19840K), 0.0259003 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
Heap after GC invocations=1 (full 0):
 def new generation   total 6144K, used 496K [0x03800000, 0x03ea0000, 0x03ea0000)
  eden space 5504K,   0% used [0x03800000, 0x03800000, 0x03d60000)
  from space 640K,  77% used [0x03e00000, 0x03e7c3a8, 0x03ea0000)
  to   space 640K,   0% used [0x03d60000, 0x03d60000, 0x03e00000)
 tenured generation   total 13696K, used 4096K [0x03ea0000, 0x04c00000, 0x04c00000)
   the space 13696K,  29% used [0x03ea0000, 0x042a0040, 0x042a0200, 0x04c00000)
 Metaspace       used 79K, capacity 2244K, committed 2368K, reserved 4480K
}

...

{Heap before GC invocations=3 (full 1):
 def new generation   total 6144K, used 5223K [0x03800000, 0x03ea0000, 0x03ea0000)
  eden space 5504K,  94% used [0x03800000, 0x03d19fe0, 0x03d60000)
  from space 640K,   0% used [0x03d60000, 0x03d60000, 0x03e00000)
  to   space 640K,   0% used [0x03e00000, 0x03e00000, 0x03ea0000)
 tenured generation   total 13696K, used 12784K [0x03ea0000, 0x04c00000, 0x04c00000)
   the space 13696K,  93% used [0x03ea0000, 0x04b1c008, 0x04b1c200, 0x04c00000)
 Metaspace       used 80K, capacity 2244K, committed 2368K, reserved 4480K
3.864: [Full GC (Allocation Failure) 3.864: [Tenured: 12784K->12784K(13696K), 0.0043278 secs] 18007K->17904K(19840K),
 [Metaspace: 80K->80K(4480K)], 0.0050660 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap after GC invocations=4 (full 2):
 def new generation   total 6144K, used 5120K [0x03800000, 0x03ea0000, 0x03ea0000)
  eden space 5504K,  93% used [0x03800000, 0x03d00138, 0x03d60000)
  from space 640K,   0% used [0x03d60000, 0x03d60000, 0x03e00000)
  to   space 640K,   0% used [0x03e00000, 0x03e00000, 0x03ea0000)
 tenured generation   total 13696K, used 12784K [0x03ea0000, 0x04c00000, 0x04c00000)
   the space 13696K,  93% used [0x03ea0000, 0x04b1c008, 0x04b1c200, 0x04c00000)
 Metaspace       used 80K, capacity 2244K, committed 2368K, reserved 4480K
}

...

java.lang.OutOfMemoryError: Java heap space
count: 17
	at com.johnfnash.learn.jvm.mem.HeapOOM$OOMObject.<init>(HeapOOM.java:12)
	at com.johnfnash.learn.jvm.mem.HeapOOM.main(HeapOOM.java:20)
Heap
 def new generation   total 6144K, used 5320K [0x03800000, 0x03ea0000, 0x03ea0000)
  eden space 5504K,  96% used [0x03800000, 0x03d32390, 0x03d60000)
  from space 640K,   0% used [0x03d60000, 0x03d60000, 0x03e00000)
  to   space 640K,   0% used [0x03e00000, 0x03e00000, 0x03ea0000)
 tenured generation   total 13696K, used 12772K [0x03ea0000, 0x04c00000, 0x04c00000)
   the space 13696K,  93% used [0x03ea0000, 0x04b191e8, 0x04b19200, 0x04c00000)
 Metaspace       used 81K, capacity 2244K, committed 2368K, reserved 4480K

其中-XX:+PrintTenuringDistribution只是丰富了gclog部分如下,用户输出显示在survivor空间里面有效的对象的岁数情况:

格式:- age 年龄: 处于当前年龄段的对象大小 总大小(各个年龄段是累计的)

Desired survivor size 327680 bytes, new threshold 1 (max 15)
- age   1:     508840 bytes,     508840 total
: 4830K->496K(6144K), 0.0145337 secs

描述信息:

这个情况是survivor空间溢出,survivor空间被占满,然后溢出的部分,直接放到了年老代(4830K - 496K = 4334K),溢出之后,计算survivor空间里头对象的年龄分布,发现年龄为1的对象大小总和超过了survivor的desired值,于是设置新的阈值为该age=1。(每次young gc之后打印survivor区域内对象的年龄分布)

如果底下age的total大小大于Desired survivor size的大小,那么就代表了survivor空间溢出了,被填满,然后会重新计算threshold。
其中 Desired survivor size 327680 bytes 是 Survivor空间大小 * 目标存活率(-XX:TargetSurvivorRatio, 可以设定,默认50%)

注:

  1. -XX:TargetSurvivorRatio: 目标存活率,默认为50%, 表明在survivor space中相同age的对象大小总和如果超过Desired survivor size,
    则重新计算threshold,以age 和 MaxTenuringThreshold的最小值为准,否则以MaxTenuringThreshold为准。
    此时,年龄大于或者等于计算出的thread的对象就直接进入老年代。

  2. -XX:MaxTenuringThreshold设置过大问题:原本应该晋升对象到survivor区,直到survivor区溢出,一旦溢出发生,Eden+Svuvivor中对象将不再依据年龄全部提升到老年代,这样对象老化的机制就失效了

    -XX:MaxTenuringThreshold设置过小:eden区的对象会陆续送入old区,对象移动本身就是开销,cms老年代回收还会造成碎片化。

参考

  1. jvm的GC日志分析

  2. Survivor空间溢出实例

  3. jvm-对象年龄(-XX:+PrintTenuringDistribution)

猜你喜欢

转载自blog.csdn.net/xxc1605629895/article/details/83833012