-
深入理解Java虛擬機(jī)
1. 輸出GC日志
通過(guò)閱讀GC日志,我們可以了解Java虛擬機(jī)內(nèi)存分配與回收策略论熙。
先來(lái)看一個(gè)簡(jiǎn)單的示例惫东,通過(guò)設(shè)置VM參數(shù)"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收集器的縮寫(xiě)跟束,它配套的新生代稱(chēng)為PSYoungGen励翼,新生代又分化eden space、from space和to space這三部分** ParOldGen**
Parallel Scavenge收集器配套的老年代Metaspace
Parallel Scavenge收集器配套的永久代total & used
總的空間和用掉的空間
2. GC日志分析
2.1 新生代Minor GC
先來(lái)回顧下垃圾回收算法,通常新生代按照8:1:1(eden space + survivor from space + survivor to space)進(jìn)行內(nèi)存劃分状土,新生產(chǎn)的對(duì)象會(huì)被放到eden space,當(dāng)eden內(nèi)存不足時(shí)伺糠,就會(huì)將存活對(duì)象移動(dòng)到survivor區(qū)域蒙谓,如果survivor空間也不夠時(shí),就需要從老年代中進(jìn)行分配擔(dān)保训桶,將存活的對(duì)象移動(dòng)老年代累驮,這就是一次Minor GC的過(guò)程。
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參數(shù)說(shuō)明
Option | Description |
---|---|
-verbose:gc | 顯示GC的操作內(nèi)容 |
-Xms20M -Xmx20M | 設(shè)置堆大小為20M |
-Xmn10M | 設(shè)置新生代的內(nèi)存空間大小為10M |
-XX:+PrintGCDetails | 打印GC中的變化 |
-XX:SurvivorRatio=8 | 新生代中Eden區(qū)域與Survivor區(qū)域的大小比值 |
-XX:+UseSerialGC | 在新生代和老年代中使用串行收集器舵揭,由于-verbose:gc參數(shù)對(duì)Parallel Scavenge收集器不起作用谤专,無(wú)法顯示顯示GC的操作內(nèi)容,因此采用串行收集器 |
- 示例代碼說(shuō)明
- 該段代碼一共創(chuàng)建了4個(gè)數(shù)組對(duì)象午绳,在給allocation4分配空間前的內(nèi)存空間使用情況如下:
- 需要執(zhí)行一次MinorGC才能給allocation4分配空間置侍,分配成功以后內(nèi)存空間使用情況如下:
2. GC日志
[GC [DefNew ... ...]
GC日志開(kāi)頭的信息通過(guò)設(shè)置-verbose:gc參數(shù)后才能輸出。
"[GC"和"[Full GC"說(shuō)明這次垃圾收集的停頓類(lèi)型,如果這次GC發(fā)生了Stop-The-World墅垮,則為"[Full GC"惕医,否則為"[GC"
"[DefNew "表示GC發(fā)生的區(qū)域?yàn)镾erial收集器的新生代中,DefNew是"Default New Generation"的縮寫(xiě)算色。Serial收集器的老年代和永久代分別表示為"Tenured"抬伺、"Perm"
** eden space 8192K, 52% used**
新生代的Eden區(qū)總共大小為8MB,使用掉的4MB是用來(lái)存放allocation4對(duì)象
tenured generation total 10240K, used 6144K
老年代大小為10MB灾梦,使用掉的6MB是用來(lái)存放allocation1峡钓、allocation2和allocation3這3個(gè)對(duì)象
2.2 大對(duì)象直接進(jìn)入老年代
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參數(shù)說(shuō)明
Option | Description |
---|---|
-XX:PretenureSizeThreshold=3145728 | 所占用內(nèi)存大于該值的對(duì)象直接分配到老年代,3145728為3MB |
-
示例代碼說(shuō)明
該段代碼創(chuàng)建了一個(gè)數(shù)組對(duì)象allocation若河,大小為4MB能岩,已經(jīng)超出PretenureSizeThreshold設(shè)置的范圍,該對(duì)象將直接被分配到老年代中萧福。
2. GC日志
-
tenured generation total 10240K, used 4096K
老年代大小為10MB拉鹃,用掉的4MB用來(lái)存放allocation對(duì)象
2.3 長(zhǎng)期存活的對(duì)象進(jìn)入老年代
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參數(shù)說(shuō)明
Option | Description |
---|---|
-XX:MaxTenuringThreshold=1 | 對(duì)象晉升為老年代的年齡閥值為1 |
-
示例代碼說(shuō)明
該段代碼創(chuàng)建了3個(gè)數(shù)組對(duì)象,當(dāng)執(zhí)行到"allocation3 = new byte[4 * _1MB]; "時(shí)鲫忍,Eden已經(jīng)被占用了256KB + 4MB膏燕,而創(chuàng)建allocation3需要4MB,已經(jīng)超過(guò)Eden的大小8MB悟民,需要先發(fā)生一次MinorGC坝辫,才能保證有空間存放allocation3
2. GC日志
設(shè)置參數(shù)為MaxTenuringThreshold=1的運(yùn)行結(jié)果
由GC日志開(kāi)頭的兩句"[GC [DefNew"可知,該段代碼一共發(fā)生了2次GC射亏,第一次是"allocation3 = new byte[4 * _1MB]; "近忙,第二次是執(zhí)行allocation3 = null時(shí)
allocation1在經(jīng)過(guò)第一次GC時(shí),對(duì)象年齡變成了1智润,由于設(shè)置的MaxTenuringThreshold=1及舍,當(dāng)發(fā)生第二次GC時(shí),allocation1的年齡已經(jīng)超出了設(shè)置的閥值窟绷,allocation1進(jìn)入到老年代击纬,因此,新生代的from space使用空間為0钾麸,對(duì)應(yīng)GC語(yǔ)句為from space 1024K, 0% used
- 設(shè)置參數(shù)為MaxTenuringThreshold=15的運(yùn)行結(jié)果
由于設(shè)置的MaxTenuringThreshold=15更振,發(fā)生第二次GC時(shí),allocation1的年齡沒(méi)有超出設(shè)置的閥值饭尝,因此肯腕,新生代的from space使用空間不為0,對(duì)應(yīng)GC語(yǔ)句為from space 1024K, 44% used
3. 參考
- 深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第2版)