回顧下,對象進入老年代的 4 個常見的時機:
- 躲過15次 GC,年齡達到 15歲時。
- 動態年齡判定規則,如果
Survivor
區域內年齡1 + 年齡2 + 年齡n 的對象總和大於Survivor
區的50%,此時年齡n以上的對象會進入老年代,不一定要達到15歲 - 如果一次
Young GC
後存活對象太多無法放入Survivor
區,此時直接計入老年代 - 大對象直接進入老年代
零、背景簡介
本主題主要研究 動態年齡判定規則
JVM
參數如下:
-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=10485760 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
-XX:NewSize=10485760
: 新生代 10MB-XX:PretenureSizeThreshold=10485760
: 老年代 10MB-XX:InitialHeapSize=20971520
: 堆總大小 20MB-XX:MaxTenuringThreshold=15
: 只要對象年齡達到15歲纔會直接進入老年代
如圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IAIBefSB-1590336245897)(img/2020-05-2119:19.png)]
總代碼如下:
public class Demo {
public static void main(String[] args) {
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[128 * 1024];
byte[] array3 = new byte[128 * 1024];
array3 = new byte[2 * 1024 * 1024];
array3 = new byte[2 * 1024 * 1024];
array3 = new byte[128 * 1024];
array3 = null;
byte[] array4 = new byte[2 * 1024 * 1024];
}
}
一、前半部分代碼gc日誌分析
public class Demo1 {
public static void main(String[] args) {
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[128 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
}
}
GC日誌:
OpenJDK 64-Bit Server VM (25.162-b12) for linux-amd64 JRE (1.8.0_162-8u162-b12-1-b12), built on Mar 15 2018 17:19:50 by "buildd" with gcc 7.3.0
Memory: 4k page, physical 16306976k(1516380k free), swap 2097148k(2067560k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
0.075: [GC (Allocation Failure) 0.075: [ParNew: 7821K->538K(9216K), 0.0016980 secs] 7821K->538K(19456K), 0.0017680 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 2752K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 27% used [0x00000000fec00000, 0x00000000fee297d0, 0x00000000ff400000)
from space 1024K, 52% used [0x00000000ff500000, 0x00000000ff586948, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
concurrent mark-sweep generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3155K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 338K, capacity 388K, committed 512K, reserved 1048576K
(1)分析
- 連續創建 3個 2MB的數組,最後置null
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[128 * 1024];
會在
eden
區創建一個 128kb 的數組同時由array2
變量來引用
byte[] array3 = new byte[2 * 1024 * 1024];
目前已有 3 × 2MB 和 128KB,這時候再加入 2MB,則會觸發 YoungGC
0.075: [GC (Allocation Failure) 0.075: [ParNew: 7821K->538K(9216K), 0.0016980 secs] 7821K->538K(19456K), 0.0017680 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
gc過後分配在 Eden
區域內的數組,如下:
這時候Survivor From
區裏的 500KB的對象 滿 1歲了。
二、後半部分gc日誌分析
用完整代碼:
public class Demo {
public static void main(String[] args) {
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[128 * 1024];
byte[] array3 = new byte[128 * 1024];
array3 = new byte[2 * 1024 * 1024];
array3 = new byte[2 * 1024 * 1024];
array3 = new byte[128 * 1024];
array3 = null;
byte[] array4 = new byte[2 * 1024 * 1024];
}
}
GC日誌
OpenJDK 64-Bit Server VM (25.162-b12) for linux-amd64 JRE (1.8.0_162-8u162-b12-1-b12), built on Mar 15 2018 17:19:50 by "buildd" with gcc 7.3.0
Memory: 4k page, physical 16306976k(1914744k free), swap 2097148k(2067560k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
0.079: [GC (Allocation Failure) 0.079: [ParNew: 7821K->547K(9216K), 0.0010760 secs] 7821K->547K(19456K), 0.0011336 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.080: [GC (Allocation Failure) 0.080: [ParNew: 6829K->0K(9216K), 0.0059605 secs] 6829K->527K(19456K), 0.0060080 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
par new generation total 9216K, used 2212K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 27% used [0x00000000fec00000, 0x00000000fee290e0, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
concurrent mark-sweep generation total 10240K, used 527K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3198K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 343K, capacity 388K, committed 512K, reserved 1048576K
(2)分析
- 看
array3
array3 = new byte[2 * 1024 * 1024];
array3 = new byte[2 * 1024 * 1024];
array3 = new byte[128 * 1024];
array3 = null;
- 接着運行
byte[] array4 = new byte[2 * 1024 * 1024];
這時候再放 2MB 數組,是放不下的,必然會觸發一次 Young GC
(3)分析 GC日誌
- 第一次 GC 前面分析過了。
0.079: [GC (Allocation Failure) 0.079: [ParNew: 7821K->547K(9216K), 0.0010760 secs] 7821K->547K(19456K), 0.0011336 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
- 第二次 GC
0.080: [GC (Allocation Failure) 0.080: [ParNew: 6829K->0K(9216K), 0.0059605 secs] 6829K->527K(19456K), 0.0060080 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
ParNew: 6829K->0K(9216K)
:
這行日誌表明,這次 GC 之後,新生代直接就沒有對象了。
可知 3 × 2MB數組 和 1 × 128KB的數組,被回收掉了。
如圖:
這時候發現 Surivior
區域中的對象都是存貨的,而且總大小超過 50% ,年齡均爲 1歲。
動態年齡判定規則
:年齡1 + 年齡2 + 年齡n的對象總大小超過了 Survivor
區域的 50%,年齡n以上的對象進入老年代。
然而這裏的對象都是 年齡1, 所以直接全部進入老年代。
如圖:
concurrent mark-sweep generation total 10240K, used 527K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
這行日誌,表明:
CMS
管理的老年代,此時使用空間剛好是 500KB多,證明了Survivor
裏的對象觸發了動態年齡判定規則,雖然沒有達到 15歲,但是全部進入老年代了。
最後 array4
變量引用的 2MB的數組,此時會被分配到 Eden
區域
如圖: