最近在学习JVM的相关知识,今天来实践一下如何阅读分析GC日志。下面我们来看一个例子,创建1000个60kb大小的对象,将他们添加到list中,设置参数打印出GC,再结合jdk自带的VirsualVM来进行学习分析。我用的是idea,如果是eclipse的话也是大同小异的,方法如下:首先在运行前点击Edit Configurations-->VM options:接着对参数进行设置。接着说一下这些参数的含义。
1和2没有区别,都是输出GC的简单日志,3是打印出GC的相关细节,所以一般我们就用-XX:+PrintGCDetails,4是打印此次垃圾回收距离jvm开始运行的所耗时间,可读性较差,不利于定位问题,而5记录的系统时间,所以一般使用-XX:+PrintGCTimeStamps。
参数:
-Xms100m
-Xmx100m
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
package tree;
/**
* Created by Hollake on 2019\5\21 0021.
*/
import java.util.ArrayList;
import java.util.List;
public class JvmTest {
public byte[] placeholder = new byte[60*1024];
public static void main(String[] args) throws Exception {
fileHeap(1000);
// System.gc();
}
public static void fileHeap(int num) throws InterruptedException{
List list = new ArrayList<>();
for (int i = 0; i < num; i++) {
// 方便观察
Thread.sleep(100);
list.add(new JvmTest());
}
System.gc();
}
}
输出结果:
2019-05-22T22:30:51.583+0800: [GC (Allocation Failure) [PSYoungGen: 25581K->4072K(29696K)] 25581K->14827K(98304K), 0.0148188 secs] [Times: user=0.05 sys=0.02, real=0.02 secs]
2019-05-22T22:31:26.504+0800: [GC (Allocation Failure) [PSYoungGen: 29672K->4080K(29696K)] 40427K->35748K(98304K), 0.0218075 secs] [Times: user=0.03 sys=0.03, real=0.02 secs]
2019-05-22T22:32:01.228+0800: [GC (Allocation Failure) [PSYoungGen: 29647K->4080K(29696K)] 61315K->56646K(98304K), 0.0205083 secs] [Times: user=0.01 sys=0.05, real=0.02 secs]
2019-05-22T22:32:01.248+0800: [Full GC (Ergonomics) [PSYoungGen: 4080K->0K(29696K)] [ParOldGen: 52565K->56543K(68608K)] 56646K->56543K(98304K), [Metaspace: 9296K->9296K(1058816K)], 0.0508283 secs] [Times: user=0.13 sys=0.00, real=0.05 secs]
Heap
PSYoungGen total 29696K, used 7152K [0x00000000fdf00000, 0x0000000100000000, 0x0000000100000000)
eden space 25600K, 27% used [0x00000000fdf00000,0x00000000fe5fc058,0x00000000ff800000)
from space 4096K, 0% used [0x00000000ff800000,0x00000000ff800000,0x00000000ffc00000)
to space 4096K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x0000000100000000)
ParOldGen total 68608K, used 56543K [0x00000000f9c00000, 0x00000000fdf00000, 0x00000000fdf00000)
object space 68608K, 82% used [0x00000000f9c00000,0x00000000fd337fd8,0x00000000fdf00000)
Metaspace used 9311K, capacity 9654K, committed 9984K, reserved 1058816K
class space used 1076K, capacity 1159K, committed 1280K, reserved 1048576K
堆和Metaspace区域的分布图
可以看出主要分为5个区域,分别为Eden、Survivor0、Survivor1、Old Gen、Metaspace五个区域,其中前三个是年轻代,而Old Gen为老年代,Metaspace是原数据区域,jdk1.8之前为永生代(Perm Gen)。
我们一行一行的来阅读分析:
2019-05-22T22:30:51.583+0800: [GC (Allocation Failure) [PSYoungGen: 25581K->4072K(29696K)] 25581K->14827K(98304K), 0.0148188 secs] [Times: user=0.05 sys=0.02, real=0.02 secs]
接下里的2次minor GC类似就不做介绍了,接着我们看最后一次的Full GC。
2019-05-22T22:32:01.248+0800: [Full GC (Ergonomics) [PSYoungGen: 4080K->0K(29696K)] [ParOldGen: 52565K->56543K(68608K)] 56646K->56543K(98304K), [Metaspace: 9296K->9296K(1058816K)], 0.0508283 secs] [Times: user=0.13 sys=0.00, real=0.05 secs]
Ergonomics
表示JVM内部环境认为此时可以进行一次垃圾收集。最后我们看一下输出的heap的总信息。
Heap
PSYoungGen total 29696K, used 7152K [0x00000000fdf00000, 0x0000000100000000, 0x0000000100000000)
eden space 25600K, 27% used [0x00000000fdf00000,0x00000000fe5fc058,0x00000000ff800000)
from space 4096K, 0% used [0x00000000ff800000,0x00000000ff800000,0x00000000ffc00000)
to space 4096K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x0000000100000000)
ParOldGen total 68608K, used 56543K [0x00000000f9c00000, 0x00000000fdf00000, 0x00000000fdf00000)
object space 68608K, 82% used [0x00000000f9c00000,0x00000000fd337fd8,0x00000000fdf00000)
Metaspace used 9311K, capacity 9654K, committed 9984K, reserved 1058816K
class space used 1076K, capacity 1159K, committed 1280K, reserved 1048576K
最后我们看一下程序运行完后jdk自带的VisualVM所展示的结果:
程序设定生成了60MB的对象,如果生成的对象大于老年代的最大容量,那么在最后进行多次Full GC无果后会抛出OOM异常。下面我们再次进行实验。
我们将代码中的64改为100,每个对象约为10k,那么循环1000次就是10000K,除去两个Survivor区,heap内存会小于100MB,所以应该会抛出OOM异常。
public byte[] placeholder = new byte[100*1024];
实验结果:
2019-05-23T15:50:34.951+0800: [GC (Allocation Failure) [PSYoungGen: 27646K->3052K(30720K)] 27646K->21863K(99328K), 0.0233884 secs] [Times: user=0.02 sys=0.05, real=0.02 secs]
2019-05-23T15:50:37.401+0800: [GC (Allocation Failure) [PSYoungGen: 30669K->3052K(30720K)] 49480K->46478K(99328K), 0.0244951 secs] [Times: user=0.02 sys=0.05, real=0.02 secs]
2019-05-23T15:50:37.425+0800: [Full GC (Ergonomics) [PSYoungGen: 3052K->0K(30720K)] [ParOldGen: 43426K->46401K(68608K)] 46478K->46401K(99328K), [Metaspace: 8811K->8811K(1056768K)], 0.0435525 secs] [Times: user=0.16 sys=0.01, real=0.04 secs]
2019-05-23T15:50:40.146+0800: [Full GC (Ergonomics) [PSYoungGen: 27595K->5000K(30720K)] [ParOldGen: 46401K->68122K(68608K)] 73997K->73122K(99328K), [Metaspace: 8930K->8930K(1056768K)], 0.0663073 secs] [Times: user=0.11 sys=0.02, real=0.07 secs]
2019-05-23T15:50:42.413+0800: [Full GC (Ergonomics) [PSYoungGen: 27571K->27013K(30720K)] [ParOldGen: 68122K->68118K(68608K)] 95693K->95132K(99328K), [Metaspace: 8942K->8942K(1056768K)], 0.0461335 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]
2019-05-23T15:50:42.509+0800: [Full GC (Ergonomics) [PSYoungGen: 27592K->26913K(30720K)] [ParOldGen: 68118K->68383K(68608K)] 95711K->95297K(99328K), [Metaspace: 8942K->8887K(1056768K)], 0.0788523 secs] [Times: user=0.25 sys=0.00, real=0.08 secs]
2019-05-23T15:50:42.638+0800: [Full GC (Ergonomics) [PSYoungGen: 27642K->27414K(30720K)] [ParOldGen: 68383K->68383K(68608K)] 96026K->95797K(99328K), [Metaspace: 8895K->8895K(1056768K)], 0.0243008 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
2019-05-23T15:50:42.672+0800: [Full GC (Ergonomics) [PSYoungGen: 27573K->27515K(30720K)] [ParOldGen: 68383K->68383K(68608K)] 95956K->95898K(99328K), [Metaspace: 8906K->8906K(1056768K)], 0.0255330 secs] [Times: user=0.06 sys=0.00, real=0.03 secs]
2019-05-23T15:50:42.708+0800: [Full GC (Ergonomics) [PSYoungGen: 27615K->27615K(30720K)] [ParOldGen: 68383K->68383K(68608K)] 95998K->95998K(99328K), [Metaspace: 8906K->8906K(1056768K)], 0.0132191 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2019-05-23T15:50:42.741+0800: [Full GC (Ergonomics) [PSYoungGen: 27615K->27615K(30720K)] [ParOldGen: 68583K->68583K(68608K)] 96198K->96198K(99328K), [Metaspace: 8906K->8906K(1056768K)], 0.0129819 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
2019-05-23T15:50:42.754+0800: [Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at tree.JvmTest.(JvmTest.java:9)
at tree.JvmTest.fileHeap(JvmTest.java:20)
at tree.JvmTest.main(JvmTest.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
可以看出JVM最后进行多次Full GC无果后抛出了 java.lang.OutOfMemoryError: Java heap space,从VisualVM也可以看出
可以看出Eden区域大小呈现线性增加,在容量不够容纳新对象的时候会进行Minor GC,将对象从Eden和其中一个Survivor复制到另外一个Survivor,而在Full GC时,一定也会进行Minor GC,除了Minor应该有的动作外,Survivor中的对象还会经过JVM的判断,将认为可以移动到老年代中的对象移动到老年代。
现在我们知道,在新生代内存不足,无法存放要生成的对象时,Minor GC会进行,但是Full GC何时进行呢?可以看参考这篇文章https://blog.csdn.net/Hollake/article/details/90484027
相关文献:
jstat命令查看jvm的GC情况 (以Linux为例)
jdk8 Metaspace 调优
参考文献:
https://blog.csdn.net/renfufei/article/details/54885190
《深入理解Java虚拟机》