一、環境
JDK |
垃圾收集器 |
是否啟用TLAB |
通用JVM引數(堆記憶體分配見下圖) |
1.6.0_65 |
Serial + Serial Old |
否 |
-Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8 |
二、說明
Minor GC
- 發生在
新生代
,當Eden
區域沒有足夠空間進行分配
- Java物件大多具有
短命
的特性
- Minor GC
非常頻繁
,速度也比較快
Major GC / Full GC
發生在老年代
- 出現Major GC,
經常
伴隨至少一次Minor GC
SpeedOf (Minor GC) ≈ 10 * SpeedOf (Major GC)
三、示例
1. 物件優先分配在Eden區
1.1 說明
- 新物件,優先考慮分配在
Eden
區域
- 如果Eden區域沒有
足夠的空間
容納新物件,進行GC
- 如果老年代有
足夠的連續空間
用來儲存所有新生代物件(或歷次晉升的平均大小) ⇒ Minor GC
- 如果物件太大,以至於
Survivor
區域無法容納
,物件直接晉升到老年代
- 否則使用複製演算法,
複製
到Survivor區域
- 否則 ⇒ 先進行一次
Minor GC
,若仍不滿足上述條件,進行Full GC
1.2 程式碼
# 程式碼
public class TestAllocation {
private static final int _1MB = 1024 * 1024;
// JVM Args:
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
public static void main(String[] args) throws InterruptedException {
System.out.println("===1. start full gc start===");
System.gc();
System.out.println("===1. start full gc end===\n");
System.out.println("===2. gc logs===");
byte[] a1 = new byte[_1MB / 4];
byte[] a2 = new byte[4 * _1MB];
byte[] a3 = new byte[4 * _1MB];// 一次Minor GC
byte[] a4 = new byte[4 * _1MB];// 一次Minor GC
byte[] a5 = new byte[4 * _1MB];// 一次Minor GC + 一次Full GC ⇒ OOM
}
}
1.3 執行結果
# 僅保留關鍵資訊
===1. start full gc start===
[Full GC (System) [Tenured: 0K->426K(10240K)] 1974K->426K(19456K)
===1. start full gc end===
===2. gc logs===
[GC [DefNew: 4843K->256K(9216K)] 5270K->4778K(19456K)]
[GC [DefNew: 4352K->256K(9216K)] 8874K->8874K(19456K)]
[GC [DefNew: 4436K->4436K(9216K)][Tenured: 8618K->8618K(10240K)] 13055K->12970K(19456K)
[Full GC [Tenured: 8618K->8549K(10240K)] 12970K->12902K(19456K)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap
def new generation total 9216K, used 4657K
eden space 8192K, 56% used
from space 1024K, 0% used
to space 1024K, 0% used
tenured generation total 10240K, used 8549K
the space 10240K, 83% used
1.4 執行示意圖
1.5 執行過程解析
- System.gc() ⇒
Full GC
新生代
已分配記憶體:0K
老年代
已分配記憶體:426K
- byte[] a1 = new byte[_1MB / 4];
- byte[] a2 = new byte[4 * _1MB];
- byte[] a3 = new byte[4 * _1MB];
- Eden空間不足,不能為a3分配4096K記憶體,需要進行GC
- 4.1 判定是否需要Full GC
- 老年代
未分配的連續空間
:10240-427 = 9813K
- 所有的新生代物件:sizeof(a1)+size(a2)=256+4096 =
4352K
9813
≫ 4352
⇒ Minor GC
- 4.2 進行Minor GC
- 1024 > 256 ⇒ remainingSpaceOf(Survivor) > sizeof(a1) ⇒
a1複製到to(Survivor區)
- 4096 > 1024 ⇒ sizeof(a2) > sizeof(Survivor) ⇒
a2晉升到Tenured區
- 4.3
Minor GC
之後,Eden空間充足,為a3
分配4096K
記憶體
- 新生代 – 4352K
- Eden:4096K(
a3
)
- from(Survivor):256K(
a1
)
- to(Survivor):0K
- 老年代 – 4522K
- byte[] a4 = new byte[4 * _1MB];
- Eden空間不足,不能為a4分配4096K記憶體,需要進行GC
- 5.1 判定是否需要Full GC
- 老年代
未分配的連續空間
:10240-427-4096 = 5717K
- 所有的新生代物件:sizeof(a1)+size(a2)=256+4096 =
4352K
5717
≫ 4352
⇒ Minor GC
- 5.2 進行Minor GC
- 第二次GC ⇒
a1由from複製到to
- 4096 > 1024 ⇒ sizeof(a3) > sizeof(Survivor) ⇒
a3晉升到Tenured區
- 5.3
Minor GC
之後,Eden空間充足,為a4
分配4096K
記憶體
- 新生代 – 4436K
- Eden:84K + 4096K(
a4
)
- from(Survivor):0K
- to(Survivor):256K(
a1
)
- 老年代 – 8618K
- Tenured:426K + 4096K(
a2
) + 4096K(a3
)
- byte[] a5 = new byte[4 * _1MB];
- Eden空間不足,不能為a5分配4096K記憶體,需要進行GC
- 6.1 判定是否需要Full GC
- 老年代
未分配的連續空間
:10240-427-4096-4096 = 1621K
- 所有的新生代物件:sizeof(a1)+size(a2)=256+4096 =
4352K
1621
≪ 4352
⇒ Minor GC
+ Full GC
- 6.2 進行Minor GC
- 第三次GC ⇒
a1由to複製到from
- 4096 > 1621 ⇒ sizeof(a4) > remainingSpaceOf(Tenured) ⇒
a4無法晉升到Tenured區
- 6.3
Minor GC
之後,堆空間記憶體佈局沒有變化
,Eden空間依舊不足,進行Full GC
- 老年代減少了69K,忽略不計,依然保留著
a2
和a3
- 新生代增加了17k,忽略不計,依然保留著
a1
和a4
- 6.4
Full GC
之後,新生代和老年代記憶體都不足以為a5
分配記憶體,丟擲OOM異常
2. 大物件
直接進入老年代
2.1 說明
- 大物件:需要
大量連續記憶體空間
的Java物件,如長字串
和大陣列
-XX:PretenureSizeThreshold=?
(Byte)
- 新物件的大小大於PretenureSizeThreshold,直接分配在
老年代
- 作用
- 新生代採用的是複製演算法,
避免
Eden和Survivor之間的記憶體複製
降低GC頻率
2.2 程式碼
public class TestPretenureSizeThreshold {
private static final int _1MB = 1024 * 1024;
// JVM Args
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
// -XX:PretenureSizeThreshold=3145728
public static void main(String[] args) {
byte[] a1 = new byte[4 * _1MB]; // 直接分配到tenured
byte[] a2 = new byte[4 * _1MB]; // 直接分配到tenured,無需GC
}
}
2.3 執行結果
# 僅保留關鍵資訊
Heap
def new generation total 9216K, used 2302K
eden space 8192K, 28% used
from space 1024K, 0% used
to space 1024K, 0% used
tenured generation total 10240K, used 8192K
the space 10240K, 80% used
2.4 執行過程解析
- sizeof(a1) > PretenureSizeThreshold ⇒ 直接分配在
Tenured
- sizeof(a2) > PretenureSizeThreshold ⇒ 直接分配在
Tenured
- 如果不設定PretenureSizeThreshold,在分配a2前會產生一次
Minor GC
,最終a1
在Tenured
,a2
在Eden
3. 長期存活
的物件晉升到老年代
3.1 說明
- 物件年齡:物件每經歷一次
Minor GC
,物件年齡加1
- JVM引數:
-XX:MaxTenuringThreshold
=?,預設是15
3.2 程式碼
public class TestTenuringThreshold {
private static final int _1MB = 1024 * 1024;
private static final int MAX_OBJ_AGE = 2;
// JVM Args
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
// -XX:MaxTenuringThreshold=3
public static void main(String[] args) {
System.out.println("===1. start full gc start===");
System.gc();
System.out.println("===1. start full gc end===\n");
System.out.println("===2. gc logs===");
byte[] a1 = new byte[_1MB / 4];
byte[] a2 = new byte[4 * _1MB];
byte[] a3 = null;
for (int i = 0; i < MAX_OBJ_AGE; ++i) {
// 執行一次, a1的年齡加1 , 最終a1的年齡為MAX_OBJ_AGE
// 如果 MAX_OBJ_AGE > MaxTenuringThreshold ⇒ a1 晉升到 Tenured
// 如果 MAX_OBJ_AGE ≦ MaxTenuringThreshold ⇒ a1 停留在 Survivor
a3 = null;
a3 = new byte[4 * _1MB];
}
}
}
3.3 執行結果
# 僅保留關鍵資訊
# MAX_OBJ_AGE = 3時
===1. start full gc start===
[Full GC (System) [Tenured: 0K->430K(10240K)] 1974K->430K(19456K)
===1. start full gc end===
===2. gc logs===
[GC [DefNew: 4679K->256K(9216K)] 5110K->4783K(19456K)]
[GC [DefNew: 4352K->256K(9216K)] 8879K->4783K(19456K)]
[GC [DefNew: 4352K->256K(9216K)] 8879K->4783K(19456K)]
Heap
def new generation total 9216K, used 4516K
eden space 8192K, 52% used
from space 1024K, 25% used
to space 1024K, 0% used
tenured generation total 10240K, used 4526K
the space 10240K, 44% used
# MAX_OBJ_AGE = 4時
===1. start full gc start===
[Full GC (System) [Tenured: 0K->426K(10240K)] 1974K->426K(19456K)
===1. start full gc end===
===2. gc logs===
[GC [DefNew: 4843K->256K(9216K)] 5270K->4779K(19456K)]
[GC [DefNew: 4437K->256K(9216K)] 8960K->4779K(19456K)]
[GC [DefNew: 4409K->257K(9216K)] 8931K->4780K(19456K)]
[GC [DefNew: 4391K->0K(9216K)] 8913K->4780K(19456K)]
Heap
def new generation total 9216K, used 4449K
eden space 8192K, 54% used
from space 1024K, 0% used
to space 1024K, 0% used
tenured generation total 10240K, used 4779K
the space 10240K, 46% used
3.4 執行過程解析
a1
和a2
現在Eden
分配記憶體
- 第一次為
a3
分配記憶體時,必須進行Minor GC
,a2
被晉升Tenured
,a3
分配在Eden
,a1
被複制到from(Survivor
),此時a1
的物件年齡為1
- 隨後每次迴圈中,先將
a3
置為null
,a3
原先引用的記憶體變成了垃圾
,後續可回收
- 再次為
a3
分配記憶體,此時Eden
區記憶體不足,進行Minor GC
- 目前存活的物件僅僅是
a1
,將其複製到to(另一塊Survivor
),a1
的物件年齡加1
- 將
from
和Eden
區清空,並在Eden為a3
分配記憶體
- MAX_OBJ_AGE = 3
≦
MaxTenuringThreshold時
- from space 1024K,
25%
used ⇒ a1
依舊停留在Survivor
- MAX_OBJ_AGE = 4
>
MaxTenuringThreshold時
- [GC [DefNew:
4391K->0K
(9216K)] 8913K->4780K(19456K)] ⇒ a1
晉升到Tenured
- from space 1024K,
0%
used ⇒ Survivor
已經無存活物件
4. 動態物件年齡
判定
4.1 說明
- 在Survivor中
相同年齡
(物件年齡 ≧ 2
)的所有物件大小之和
≧ 0.5 * sizeof(Survivor)
⇒ 大於或等於
該年齡的物件直接晉升到老年代
,無須考慮MaxTenuringThreshold
4.2 程式碼一(單個1/2
物件)
public class TestDynamicObjectAge {
private static final int _1MB = 1024 * 1024;
private static final int MAX_OBJ_AGE = 1;
// JVM Args
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
// -XX:MaxTenuringThreshold=3
public static void main(String[] args) {
int al;
System.out.println("===1. start full gc start===");
System.gc();
System.out.println("===1. start full gc end===\n");
System.out.println("===2. gc logs===");
byte[] a1 = new byte[_1MB / 2]; # 單個1/2物件
byte[] a2 = new byte[4 * _1MB];
byte[] a3 = null;
for (int i = 0; i < MAX_OBJ_AGE; ++i) {
// 相同年齡(物件年齡 ≧ 2)的所有物件大小之和 ≧ 0.5 * sizeof(Survivor)
// ⇒ 大於或等於該年齡的物件直接晉升到老年代,無需考慮MaxTenuringThreshold
a3 = null;
a3 = new byte[4 * _1MB];
}
}
}
4.3 程式碼一執行結果
# 僅保留關鍵資訊
# MAX_OBJ_AGE = 1時
===1. start full gc start===
[Full GC (System) [Tenured: 0K->429K(10240K)] 1974K->429K(19456K)
===1. start full gc end===
===2. gc logs===
[GC [DefNew: 5099K->513K(9216K)] 5529K->5039K(19456K)]
Heap
def new generation total 9216K, used 4773K
eden space 8192K, 52% used
from space 1024K, 50% used # a1的物件年齡為1,依舊停留在Survivor區域
to space 1024K, 0% used
tenured generation total 10240K, used 4525K
the space 10240K, 44% used
# MAX_OBJ_AGE = 2時
===1. start full gc start===
[Full GC (System) [Tenured: 0K->426K(10240K)] 1974K->426K(19456K)
===1. start full gc end===
===2. gc logs===
[GC [DefNew: 5099K->513K(9216K)] 5526K->5036K(19456K)]
[GC [DefNew: 4694K->1K(9216K)] 9216K->5037K(19456K)]
# a1的物件年齡為2 < MaxTenuringThreshold,且a2等於0.5 * sizeof(Survivor),直接晉升到老年代
Heap
def new generation total 9216K, used 4317K
eden space 8192K, 52% used
from space 1024K, 0% used
# a1的物件年齡為2 < MaxTenuringThreshold,且a2等於0.5 * sizeof(Survivor),直接晉升到老年代
to space 1024K, 0% used
tenured generation total 10240K, used 5036K
the space 10240K, 49% used
4.4 程式碼二(四個1/8
物件)
public class TestDynamicObjectAge {
private static final int _1MB = 1024 * 1024;
private static final int MAX_OBJ_AGE = 1;
// JVM Args
// -XX:+PrintGCDetails
// -XX:+UseSerialGC
// -Xms20m -Xmx20m
// -Xmn10m -XX:SurvivorRatio=8
// -XX:MaxTenuringThreshold=3
public static void main(String[] args) {
int al;
System.out.println("===1. start full gc start===");
System.gc();
System.out.println("===1. start full gc end===\n");
System.out.println("===2. gc logs===");
byte[] a1_1 = new byte[_1MB / 8]; # 四個1/8物件
byte[] a1_2 = new byte[_1MB / 8];
byte[] a1_3 = new byte[_1MB / 8];
byte[] a1_4 = new byte[_1MB / 8];
byte[] a2 = new byte[4 * _1MB];
byte[] a3 = null;
for (int i = 0; i < MAX_OBJ_AGE; ++i) {
// 相同年齡(物件年齡 ≧ 2)的所有物件大小之和 ≧ 0.5 * sizeof(Survivor)
// ⇒ 大於或等於該年齡的物件直接晉升到老年代,無需考慮MaxTenuringThreshold
a3 = null;
a3 = new byte[4 * _1MB];
}
}
}
4.5 程式碼二執行結果
# 僅保留關鍵資訊
# MAX_OBJ_AGE = 1時
===1. start full gc start===
[Full GC (System) [Tenured: 0K->427K(10240K)] 1974K->427K(19456K)
===1. start full gc end===
===2. gc logs===
[GC [DefNew: 4971K->514K(9216K)] 5399K->5038K(19456K)]
Heap
def new generation total 9216K, used 4859K
eden space 8192K, 53% used # a1_1、a1_2、a1_3、a1_4的物件年齡為1,依舊停留在Survivor區域
from space 1024K, 50% used
to space 1024K, 0% used
tenured generation total 10240K, used 4523K
the space 10240K, 44% used
# MAX_OBJ_AGE = 2時
===1. start full gc start===
[Full GC (System) [Tenured: 0K->427K(10240K)] 1974K->427K(19456K)
===1. start full gc end===
===2. gc logs===
[GC [DefNew: 4971K->514K(9216K)] 5399K->5037K(19456K)]
[GC [DefNew: 4694K->0K(9216K)] 9218K->5037K(19456K)]
# a1_1、a1_2、a1_3和a1_4的物件年齡為2 < MaxTenuringThreshold,且它們之和等於0.5 * sizeof(Survivor),晉升到老年代
Heap
def new generation total 9216K, used 4316K
eden space 8192K, 52% used
from space 1024K, 0% used
# a1_1、a1_2、a1_3和a1_4的物件年齡為2 < MaxTenuringThreshold,且它們之和等於0.5 * sizeof(Survivor),晉升到老年代
to space 1024K, 0% used
tenured generation total 10240K, used 5037K
the space 10240K, 49% used
compacting perm gen total 21248K, used 4965K
the space 21248K, 23% used
三、參考資料