JVM-GC日志详细分析
1.打印GC日志参数
1.3 基本JVM参数
参数示例 | 描述说明 |
---|---|
-verbose:gc | 控制台打印GC参数 |
-Xms20M | 初始堆大小 20M |
-Xmx20M | 最大堆大小20M 一般情况下-Xms和-Xmx这两个值设为相同大小 |
-Xmn10M | 新生代最大可用值10M |
-XX:+PrintGC | 触发GC时日志打印 |
-XX:+PrintGCDetails | 触发GC时日志打印详细 |
–XX:UseSerialGC | 串行回收 |
-XX:SurvivorRatio=8 | eden:from:to =8:1:1 |
-XX:+HeapDumpOnOutOfMemoryErro | OOM时生成Dump文件 |
-XX:NewRatio=2 | 新生代:老年代 = 1:2 |
2.GC日志分析
2.1首先运行程序,打印GC参数及日志
设置JVM运行参数
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:SurvivorRatio=8
运行程序
package com.jzj.jvmtest.gctest;
/**
* 分析gc日志 主动通过 system.gc触发 垃圾回收
* 设置 -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:SurvivorRatio=8
*/
public class SystemGcLog {
private static final int ONE_MB = 1024 * 1024;
/**
* 设置内存20M,最大堆内存20M,新生代10M,那么就剩余10M给老年代
* 新生代 10M ,Eden:survivor = 8:1, 所以 Eden: 8M, fromSurvivor:1M, toSurvivor:1M, 新生代可用空间就是 8+1 9M
*
* @param args
*/
public static void main(String[] args) {
//定义1个 byte
byte[] allocation = new byte[ONE_MB];
//将 allocation 置为空, 让他成为 垃圾
allocation = null;
//主动调用 system.gc() 统计垃圾处理器 回收垃圾
System.gc();
}
}
3.分析GC日志
[GC (System.gc()) [PSYoungGen: 3704K->1000K(9216K)] 3704K->1042K(19456K), 0.0010446 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 1000K->0K(9216K)] [ParOldGen: 42K->819K(10240K)] 1042K->819K(19456K), [Metaspace: 3399K->3399K(1056768K)], 0.0034435 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 9216K, used 246K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 3% used [0x00000000ff600000,0x00000000ff63d890,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 819K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 7% used [0x00000000fec00000,0x00000000fecccc28,0x00000000ff600000)
Metaspace used 3406K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
3.1 GC日志的分析
[GC (System.gc()) [PSYoungGen: 3704K->1000K(9216K)] 3704K->1042K(19456K), 0.0010446 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC的类别] =》 [gc发生区域]
[PSYoungGen: 3704K->1000K(9216K)] =》 3704K gc发生前,该区域使用的容量,->1000K, gc发生后,该区域使用的容量,(9216K)当前区域的总容量]
外层的3704K->1042K(19456K) =》 3704K->1042K(19456K) 3704K gc前 堆使用的大小, ->1042K gc后,堆使用的容量,19456是堆空间的总容量
[Times: user=0.00 sys=0.00, real=0.00 secs] =》 user:用户态消耗CPU时间,sys系统内核消耗CPU时间,real从开始到结束使用的时钟时间
-
GC (System.gc() ) 局部收集 新生代 的模式
Young GC: 只收集young gen的GC,Young GC还有种说法就叫做 “Minor GC”
Old GC: 只收集old gen的GC。只有垃圾收集器CMS的concurrent collection 是这个模式
Mixed GC: 收集整个young gen 以及部分old gen的GC。只有垃圾收集器 G1有这个模式 -
Full GC 就是收集整个堆,包括新生代,老年代,永久代(在JDK 1.8及以后,永久代会被移除,换为metaspace(元空间))等收集所有部分的模式。
不同的垃圾收集器,触发条件可能不一样
- 当准备要触发一次 young GC时,如果发现统计数据说之前 young GC的平均大小比目前的 old gen剩余的空间大,则不会触发young GC而是转为触发 full GC (因为HotSpot VM的GC里,除了垃圾回收器 CMS的concurrent collection 之外,其他能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先准备一次单独的young GC)
- 如果有永久代(perm gen) ,要在永久代分配空间但已经没有足够空间时,也要触发一次 full GC
- 执行System.gc(),heap dump带GC, 其默认都是触发 full GC
- PSYoungGen/ParOldGen 表示GC发生的区域, 我们的JVM是哦用的是Server模式,默认的垃圾收集器组合就是Parallel Scavenge垃圾收集器的新生代和使用Parallel old垃圾收集器的老生代
- [PSYoungGen: 3704K->1000K(9216K)] =》 3704K gc发生前,该区域使用的容量,->1000K, gc发生后,该区域使用的容量,(9216K)当前区域的总容量]
- 外层的3704K->1042K(19456K) =》 3704K->1042K(19456K) 3704K gc前 堆使用的大小, ->1042K gc后,堆使用的容量,19456是堆空间的总容量
- [Times: user=0.00 sys=0.00, real=0.00 secs] =》 user:用户态消耗CPU时间,sys系统内核消耗CPU时间,real从开始到结束使用的时钟时间
3.2 GC 堆日志的分析
可以看到堆的 日志如下
Heap
PSYoungGen total 9216K, used 246K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 3% used [0x00000000ff600000,0x00000000ff63d890,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 819K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 7% used [0x00000000fec00000,0x00000000fecccc28,0x00000000ff600000)
Metaspace used 3406K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
- PSYoungGen total 9216K, used 246K 堆中 新生代大小,一共9216K也就是9m,9M怎么来的?JVM参数设置的 -Xmn10M,设置的-XX:SurvivorRatio=8 说明 Eden:From:To分别是8:1:1,这样 一共10M的新生代, From和To 只有一块可以用, 所以可使用的内存就是 8+1=9M, 现在用了 246K
- eden space 8192K, 3% used Eden区域用了8M
- from space 1024K, 0% used to space 1024K, 0% From Survivor和To Survivor都是1M
- ParOldGen total 10240K, used 819K 老年代一共10M, 10M怎么来的呢? 设置的 -Xms20M, 堆最大内存20M,其中 -Xmn10M用了10M,那么 老年代就剩余10M了, used 819k,使用了819KB
- Metaspace used 3437K, capacity 4496K, committed 4864K, reserved 1056768K
详细看下 Metaspace 细分这么多是干什么的
其中used和capacity是从类加载器的角度来衡量的
而committed和reserved是从操作系统地址空间的角度来衡量
- used:是分配给所有类加载器的metachunk中用来存储元数据的部分的大小
- capacity:分配给所有类加载器的metachunk的大小,包含每个metachunk的head、used、wasted和current metachunk的free部分
- committed:向OS申请的内存中已经分配物理内存的大小,包含VirtualSpaceList中所有node已经commit的部分(也就是非当前node+当前node commit的部分),或者说所有类加载器的capacity+全局空闲链表Chunk Manager+硬件预留HWM margin
- reserved:JVM进程虚拟地址空间的部分,像class metaspace默认CompressedClassSpaceSize是1GB,所以reserved就是1GB,日志中的metaspace包含class space和no-class space。
3.2 GC Metaspace元空间分析
JDK 1.7和 JDK 1.8 中,会出现堆内存溢出,并且 JDK 1.8中 PermSize 和 MaxPermGen 已经无效。因此,可以大致验证 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代的结论。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集