對象優先在Eden配置設定
大多數情況下,對象在新生代Eden區中配置設定。當Eden區沒有足夠空間進行配置設定時,虛拟機将發起 一次Minor GC。
大對象直接進入老年代
大對象就是指需要大量連續記憶體空間的Java對象,最典型的大對象便是那種很長的字元串,或者 元素數量很龐大的數組,本節例子中的byte[]數組就是典型的大對象。大對象對虛拟機的記憶體配置設定來說 就是一個不折不扣的壞消息,比遇到一個大對象更加壞的消息就是遇到一群“朝生夕滅”的“短命大對 象”,我們寫程式的時候應注意避免。在Java虛拟機中要避免大對象的原因是,在配置設定空間時,它容易 導緻記憶體明明還有不少空間時就提前觸發垃圾收集,以擷取足夠的連續空間才能安置好它們,而當複 制對象時,大對象就意味着高額的記憶體複制開銷。HotSpot虛拟機提供了-XX:PretenureSizeThreshold 參數,指定大于該設定值的對象直接在老年代配置設定,這樣做的目的就是避免在Eden區及兩個Survivor區 之間來回複制,産生大量的記憶體複制操作。
/**
* @author Wen
* 代碼清單3-8 大對象直接進入老年代
* VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728 -XX:+UseSerialGC
*/
public class test1 {
private static final int _1MB = 1024 * 1024;
public static void testPretenureSizeThreshold() {
byte[] allocation;
//直接配置設定在老年代中
allocation = new byte[4 * _1MB];
}
public static void main(String[] args) {
testPretenureSizeThreshold();
}
}
Heap
def new generation total 9216K, used 3054K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 9% used [0x00000000fec00000, 0x00000000feefbb38, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 40% used [0x00000000ff600000, 0x00000000ffc00010, 0x00000000ffc00200, 0x0000000100000000)
Metaspace used 3250K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K
來 假裝分析一波
首先我們通過-XX:SurvivorRatio=8這個參數來指定Eden和Survivor的From、To區域的記憶體比例為8:1:1。
-Xms20M -Xmx20M -Xmn10M 來指定堆的大小為20M不可擴充且設定年輕代的大小為10M,
通過設定-XX:PretenureSizeThreshold=3145728的大小來限制當對象大于這值時,直接進入老年代.
通過日志列印可以看出Survivor區域并沒有被使用,老年代被使用了40%是以我們建立的allocation 直接被配置設定到了老年代
長期存活的對象将進入老年代
HotSpot虛拟機中多數收集器都采用了分代收集來管理堆記憶體,那記憶體回收時就必須能決策哪些存 活對象應當放在新生代,哪些存活對象放在老年代中。為做到這點,虛拟機給每個對象定義了一個對 象年齡(Age)計數器,存儲在對象頭中。對象通常在Eden區裡誕生,如果經過第一次 Minor GC後仍然存活,并且能被Survivor容納的話,該對象會被移動到Survivor空間中,并且将其對象 年齡設為1歲。對象在Survivor區中每熬過一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程 度(預設為15),就會被晉升到老年代中。對象晉升老年代的年齡門檻值,可以通過參數-XX: MaxTenuringThreshold設定。
當-XX:MaxTenuringThreshold=1
/**
* @author Wen
* VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution -XX:+UseSerialGC
*/
public class test2 {
private static final int _1MB = 1024 * 1024;
@SuppressWarnings("unused")
public static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
// 什麼時候進入老年代決定于XX:MaxTenuringThreshold設定
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[] args) {
testTenuringThreshold();
}
}
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 1048576 bytes, 1048576 total
: 7243K->1024K(9216K), 0.0029620 secs] 7243K->5314K(19456K), 0.0029979 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 640 bytes, 640 total
: 5203K->0K(9216K), 0.0010193 secs] 9494K->5294K(19456K), 0.0010311 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4233K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 51% used [0x00000000fec00000, 0x00000000ff022540, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400280, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 5293K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 51% used [0x00000000ff600000, 0x00000000ffb2b7b8, 0x00000000ffb2b800, 0x0000000100000000)
Metaspace used 3246K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 350K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 0
來 假裝分析er波
allocation1 = 0.25M 進入Eden = 0.25M
allocation2 = 4M 進入Eden = 4 + 0.25M
allocation3 = 4M 進入Eden = 8.25 > 8M,這時候Eden記憶體被占滿,觸發GC
這時候Survivor也放不下allocation2和allocation3 ,但是可以放下allocation1 ,這時候allocation1 進入From = 0.25M,分代年齡+1,
allocation2 進入老年代 = 4M,allocation3 進入Eden = 4M。
之後allocation3 = 4M 進入Eden,Eden滿了觸發GC,但是由于我們設定了-XX:MaxTenuringThreshold=1,意味着分代年齡達大于1的對象将送往老年代,
動态對象年齡判定
為了能更好地适應不同程式的記憶體狀況,HotSpot虛拟機并不是永遠要求對象的年齡必須達到- XX:MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于 Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到-XX: MaxTenuringThreshold中要求的年齡。
/**
* -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:+UseSerialGC -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
* @author Wen
*/
public class Test3 {
private static final int _1MB = 1024 * 1024;
public static void test(){
byte[] allocation1,allocation2,allocation3,allocation4;
// allocation1+allocation2大于survivo空間一半
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[_1MB / 4];
allocation3 = new byte[4 * _1MB];
allocation4 = new byte[4 * _1MB];
allocation4 = null;
allocation4 = new byte[4 * _1MB];
}
public static void main(String[] args) {
test();
}
}
參數分析
-Xms20M:配置設定堆的最小記憶體
-Xmx20M:配置設定堆的最大記憶體(-Xms =-Xmx表示設定堆不可動态擴充記憶體)
-Xmn10M:配置設定年輕代的記憶體
-XX:+PrintGCDetails:在控制台列印日志
-XX:+UseSerialGC:指定年輕代使用Serial搜集器
-XX:SurvivorRatio=8:指定Eden與survivor中Form區和To區記憶體占比為8:1:1
-XX:MaxTenuringThreshold=15:設定對象分代年齡達到15後進入老年代區域
-XX:+PrintTenuringDistribution:列印分代年齡
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 15)
- age 1: 1048568 bytes, 1048568 total
: 7499K->1023K(9216K), 0.0034811 secs] 7499K->5553K(19456K), 0.0035137 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 400 bytes, 400 total
: 5203K->0K(9216K), 0.0023783 secs] 9733K->9649K(19456K), 0.0023921 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4234K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 51% used [0x00000000fec00000, 0x00000000ff022758, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400190, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 9649K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 94% used [0x00000000ff600000, 0x00000000fff6c558, 0x00000000fff6c600, 0x0000000100000000)
Metaspace used 3279K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 355K, capacity 388K, committed 512K, reserved 1048576K
來 假裝分析er波
allocation1 進入Eden = _1MB/4 < 8M
allocation2 進入Eden = _1MB/4 + _1MB/4 = _1MB/2 < 8M
allocation3 進入Eden = _1MB/2 + 4M < 8M
allocation4 進入Eden = _1MB/2 + 4M + 4M > 8M 觸發GC,這時候由于Form和To都放不下allocation3,是以
allocation3直接進入老年代,allocation1、allocation2進入From區分代年齡+1,allocation4 進入Eden = 4M
allocation4 = null 為後面清理未被使用的對象做準備
allocation4 = new byte[4 * _1MB] 進入Eden = 4M + 4M >8M(因為這裡還要算上對象頭和記憶體
補白的大小是以大于8M),觸發GC,發現前面allocation4 = null 的對象未被使用,直接清除
這時候allocation1 + allocation2 大于from區_1MB/2(滿足分代年齡相等且記憶體總和大于survivor的一半)
直接進入老年代,Eden = 4M,Survivor = 0M,老年代 >4+_1MB/2