-
深入理解Java虚拟机
1. 输出GC日志
通过阅读GC日志,我们可以了解Java虚拟机内存分配与回收策略。
先来看一个简单的示例,通过设置VM参数"XX:+PrintGCDetails"就可以打印出GC日志
zhanghuamaodeMacBook-Pro:java zhanghuamao$ java -XX:+PrintGCDetails TestClass
hello
Heap
PSYoungGen total 76288K, used 3932K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
eden space 65536K, 6% used [0x000000076ab00000,0x000000076aed7240,0x000000076eb00000)
from space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
to space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
ParOldGen total 175104K, used 0K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 0% used [0x00000006c0000000,0x00000006c0000000,0x00000006cab00000)
Metaspace used 2630K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
-
PSYoungGen
PS是Parallel Scavenge收集器的缩写,它配套的新生代称为PSYoungGen,新生代又分化eden space、from space和to space这三部分 -
** ParOldGen**
Parallel Scavenge收集器配套的老年代 -
Metaspace
Parallel Scavenge收集器配套的永久代 -
total & used
总的空间和用掉的空间
2. GC日志分析
2.1 新生代Minor GC
先来回顾下垃圾回收算法,通常新生代按照8:1:1(eden space + survivor from space + survivor to space)进行内存划分,新生产的对象会被放到eden space,当eden内存不足时,就会将存活对象移动到survivor区域,如果survivor空间也不够时,就需要从老年代中进行分配担保,将存活的对象移动老年代,这就是一次Minor GC的过程。
新生代
1. 示例代码
- code
/**
* VM agrs: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
* -XX:SurvivorRatio=8 -XX:+UseSerialGC
*/
public class MinorGCTest {
private static final int _1MB = 1024 * 1024;
public static void testAllocation() {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB];
}
public static void main(String[] agrs) {
testAllocation();
}
}
- VM参数说明
Option | Description |
---|---|
-verbose:gc | 显示GC的操作内容 |
-Xms20M -Xmx20M | 设置堆大小为20M |
-Xmn10M | 设置新生代的内存空间大小为10M |
-XX:+PrintGCDetails | 打印GC中的变化 |
-XX:SurvivorRatio=8 | 新生代中Eden区域与Survivor区域的大小比值 |
-XX:+UseSerialGC | 在新生代和老年代中使用串行收集器,由于-verbose:gc参数对Parallel Scavenge收集器不起作用,无法显示显示GC的操作内容,因此采用串行收集器 |
- 示例代码说明
- 该段代码一共创建了4个数组对象,在给allocation4分配空间前的内存空间使用情况如下:
before MinorGC
- 需要执行一次MinorGC才能给allocation4分配空间,分配成功以后内存空间使用情况如下:
after MinorGC
2. GC日志
Minor GC日志
-
[GC [DefNew ... ...]
-
GC日志开头的信息通过设置-verbose:gc参数后才能输出。
-
"[GC"和"[Full GC"说明这次垃圾收集的停顿类型,如果这次GC发生了Stop-The-World,则为"[Full GC",否则为"[GC"
-
"[DefNew "表示GC发生的区域为Serial收集器的新生代中,DefNew是"Default New Generation"的缩写。Serial收集器的老年代和永久代分别表示为"Tenured"、"Perm"
-
** eden space 8192K, 52% used**
-
新生代的Eden区总共大小为8MB,使用掉的4MB是用来存放allocation4对象
-
tenured generation total 10240K, used 6144K
-
老年代大小为10MB,使用掉的6MB是用来存放allocation1、allocation2和allocation3这3个对象
2.2 大对象直接进入老年代
1. 示例代码
- code
/**
* VM agrs: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
* -XX:SurvivorRatio=8 -XX:+UseSerialGC
* -XX:PretenureSizeThreshold=3145728
*/
public class TestClass2 {
private static final int _1MB = 1024 * 1024;
public static void testPretenureSizeThreshold() {
byte[] allocation;
allocation = new byte[4 * _1MB];
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
testPretenureSizeThreshold();
}
}
- VM参数说明
Option | Description |
---|---|
-XX:PretenureSizeThreshold=3145728 | 所占用内存大于该值的对象直接分配到老年代,3145728为3MB |
- 示例代码说明
该段代码创建了一个数组对象allocation,大小为4MB,已经超出PretenureSizeThreshold设置的范围,该对象将直接被分配到老年代中。
2. GC日志
大对象直接进入老年代-GC日志
- tenured generation total 10240K, used 4096K
老年代大小为10MB,用掉的4MB用来存放allocation对象
2.3 长期存活的对象进入老年代
1. 示例代码
- code
/**
* VM agrs: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
* -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:MaxTenuringThreshold=1
*/
public class TestClass3 {
private static final int _1MB = 1024 * 1024;
public static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
public static void main(String[] agrs) {
testTenuringThreshold();
}
}
- VM参数说明
Option | Description |
---|---|
-XX:MaxTenuringThreshold=1 | 对象晋升为老年代的年龄阀值为1 |
- 示例代码说明
该段代码创建了3个数组对象,当执行到"allocation3 = new byte[4 * _1MB]; "时,Eden已经被占用了256KB + 4MB,而创建allocation3需要4MB,已经超过Eden的大小8MB,需要先发生一次MinorGC,才能保证有空间存放allocation3
2. GC日志
-
设置参数为MaxTenuringThreshold=1的运行结果
-
由GC日志开头的两句"[GC [DefNew"可知,该段代码一共发生了2次GC,第一次是"allocation3 = new byte[4 * _1MB]; ",第二次是执行allocation3 = null时
-
allocation1在经过第一次GC时,对象年龄变成了1,由于设置的MaxTenuringThreshold=1,当发生第二次GC时,allocation1的年龄已经超出了设置的阀值,allocation1进入到老年代,因此,新生代的from space使用空间为0,对应GC语句为from space 1024K, 0% used
MaxTenuringThreshold=1
- 设置参数为MaxTenuringThreshold=15的运行结果
由于设置的MaxTenuringThreshold=15,发生第二次GC时,allocation1的年龄没有超出设置的阀值,因此,新生代的from space使用空间不为0,对应GC语句为from space 1024K, 44% used
MaxTenuringThreshold=15
3. 参考
- 深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)