1. 程式人生 > >JVM:GC-記憶體分配與回收策略

JVM:GC-記憶體分配與回收策略

物件優先在Eden區分配

物件優先在eden區分配,當eden區沒有足夠空間分配記憶體時,就會發現minor gc.
程式碼例項:

public class Main {

    static int _1M = 1024*1024;
    //vm 引數
    // -verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M

    public static void main(String[] args) {
        byte[] b1;
        b1 = new byte[2*_1M];
    }
}

JVM引數:
-verbose:gc -XX:+PrintGCDetails : 列印GC日誌
-Xms20M -Xmx20M -Xmn10M :堆記憶體20M,不允許擴充套件,新生代給10M。
如上在記憶體中分配2M空間給byte陣列。
輸出如下:

Heap
 PSYoungGen      total 9216K, used 4055K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 49% used [0x00000007bf600000,0x00000007bf9f5d40,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
  to   space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
 ParOldGen       total 10240K, used 0K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf600000)
 Metaspace       used 3159K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 346K, capacity 388K, committed 512K, reserved 1048576K

可以看到只要eden區有使用記憶體,
49% used ,將近4M的空間,可是我們程式碼裡只分配了兩兆,其實還是JVM本身的一些物件吧,不深究。

大物件直接在年老代分配

通過 JVM引數-XX:PretenureSizeThreshold可以設定達到指定大小的大物件直接進入年老代。
為什麼要讓大物件直接在年老代分配呢?
避免eden區與兩個survivor區進行大量的記憶體複製,也就是避免不必要的minor gc,直接讓大物件進入年老代,就不會觸發新生代的GC。
注意:這個引數只對Serial和ParNew生效。

public class Main {

    static int _1M = 1024*1024;
    //vm 引數
    //  -XX:+PrintGCDetails -Xms20M -Xmx20M 
 //   -Xmn10M -verbose:gc 
//-XX:PretenureSizeThreshold=3145728  
//-XX:+UseSerialGC -XX:+PrintCommandLineFlags

    public static void main(String[] args) {
        byte[] b1;
        b1 = new byte[6*_1M];
    }
}

設定直接進入老年代的物件閾值為3M:
-XX:PretenureSizeThreshold=3145728

Heap
 def new generation   total 9216K, used 2047K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  24% used [0x00000007bec00000, 0x00000007bedffdc0, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  60% used [0x00000007bf600000, 0x00000007bfc00010, 0x00000007bfc00200, 0x00000007c0000000)
 Metaspace       used 3300K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 365K, capacity 388K, committed 512K, reserved 1048576K

雖然新生代空間充足,但是這個大物件直接被分配進老年代了。

長期存活物件進入老年代

JVM垃圾收集器採用分代回收的方式來實現GC。
所以記憶體回收的時候需要知道哪些物件應該在新生代,哪些物件應該放進老年代。
因此JVM為每一個物件維護了一個物件年齡計數器,當物件熬過第一次minor gc 並被survivor接納時,設定年齡為1,物件在survivor區每熬過一次minor gc 年齡就+1,當物件達到一次年齡,就會進入老年代,這個閾值預設15,可以通過-XX:MaxTenuringThreshold設定。
此外,如果survivor區同齡物件達到survivor的一半,那麼大於等於這個年齡的所有物件都會進入老年代,而無需等到閾值年齡

空間分配擔保

在進行minor gc前,會先判斷老年代最大連續空間是否大於新生代所有物件總空間,如果大於,則正常執行minor gc,此時是安全的。
如果小於,則判斷-XX:+HandlePromotionFailure是否設定為允許擔保失敗。
如果不允許,則直接full gc。
如果允許,就繼續判斷老年代最大連續空間是否大於歷次晉升到老年代物件的平均大小,如果大於,則冒險執行minor gc 。否則就full gc