天天看點

實戰:記憶體配置設定與回收政策

對象優先在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
           

空間配置設定擔保(後續補充)