標籤:
1. 對象優先在Eden分配
大多數情況下,對象在新生代Eden區中分配。當Eden區沒有足夠的空間時,虛擬機器將發起一次Minor GC。在如下的測試代碼中,嘗試分配3個2MB大小和1個4MB大小的對象,在運行時通過參數-Xmx20M,-Xms20M,-Xmn10M這三個參數限制了java堆大小為20MB,不可擴充,其中10MB分配給新生代,剩下的非配給老年代。-XX:SurvivorRatio=8決定了新生代中Eden區與一個Survivor區的比例為8:1,即 Eden: from Survivor:to Survivor = 8:1:1,即8MB:1MB:1MB,新生代的可用空間為9MB。
package test1;class TestGc {private static final int _1MB = 1024 * 1024;/** -verbose:gc-Xms20m-Xmx20m-Xmn10m-XX:+UseSerialGC-XX:+PrintGCDetails-XX:SurvivorRatio=8 * @param args */public static void main(String[] args) {byte[] allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[2 * _1MB]; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation4 = new byte[4 * _1MB];//出現一次Monor GC}}
對上面的一段代碼簡單分析:
Eden: from Survivor:to Survivor = 8MB:1MB:1MB;
分配allocation1、allocation2、allocation三個對象到Eden區,佔6MB空間;分配allocation4時 發現Eden剩餘空間2MB不夠分配,因此發生Minor GC,GC期間又發現已有的3個2MB的對象都無法放入Survivor空間(1MB),所以通過擔保機制提前轉移到老年代區(3個2MB的對象),此時Eden區恢複到8MB空間,然後將allocation4分配到Eden空間。
運行結果:
[GC [DefNew: 6487K->160K(9216K), 0.0155080 secs] 6487K->6305K(19456K), 0.0155918 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
Heap
def new generation total 9216K, used 4584K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 54% used [0x00000000f9a00000, 0x00000000f9e51f98, 0x00000000fa200000)
from space 1024K, 15% used [0x00000000fa300000, 0x00000000fa3283d0, 0x00000000fa400000)
to space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
tenured generation total 10240K, used 6144K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 60% used [0x00000000fa400000, 0x00000000faa00030, 0x00000000faa00200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2981K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 14% used [0x00000000fae00000, 0x00000000fb0e96a0, 0x00000000fb0e9800, 0x00000000fc2c0000)
No shared spaces configured.
下面的結果是上面這段代碼去掉-XX:+UseSerialGC參數後的(我的環境預設採用的是Parallel Scavenge收集器),沒有發生Minor GC,為什麼呢?這是因為不同的收集器收集策略不同(可以看下下面的Minor GC和 Full GC的差別就明別了)。
Heap
PSYoungGen total 9216K, used 6651K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 81% used [0x00000000ff600000,0x00000000ffc7eec8,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
PSOldGen total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000)
PSPermGen total 21248K, used 2972K [0x00000000f9a00000, 0x00000000faec0000, 0x00000000fec00000)
object space 21248K, 13% used [0x00000000f9a00000,0x00000000f9ce71e0,0x00000000faec0000)
Minor GC和 Full GC:
新生代GC(Minor GC):只發生在新生代的垃圾收集動作,因為java對象大多數都具備朝生夕滅的特性,所有Minor GC非常頻繁,一般回收速度也比較快。
老年代GC(Major GC/Full GC):只發生在老年代的GC,出現了Full GC,經常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge 收集器的收集策略就有直接進行Full GC的策略選擇過程)。Full GC的速度一般會比Minor GC慢10倍以上。
2.大對象直接進入老年代
所謂的大對象是指,需要大量連續記憶體空間的Java對象,最典型的大對象就是那種很長的字串以及數組。大對象對虛擬機器的對象分配來說是一個壞訊息,經常出現大對象容易導致記憶體還有不少空間就提前觸發垃圾收集器以獲得足夠的連續空間來安置它們。
/**-verbose:gc -Xms20m-Xmx20m-Xmn10m-XX:+PrintGCDetails-XX:SurvivorRatio=8-XX:+UseSerialGC-XX:PretenureSizeThreshold=3145728(3MB) */public static void test2(){byte[] allocation1; allocation1 = new byte[4 * _1MB];}
運行結果:
Heap
def new generation total 9216K, used 507K [0x00000000f9a00000, 0x00000000fa400000, 0x00000000fa400000)
eden space 8192K, 6% used [0x00000000f9a00000, 0x00000000f9a7ee98, 0x00000000fa200000)
from space 1024K, 0% used [0x00000000fa200000, 0x00000000fa200000, 0x00000000fa300000)
to space 1024K, 0% used [0x00000000fa300000, 0x00000000fa300000, 0x00000000fa400000)
tenured generation total 10240K, used 4096K [0x00000000fa400000, 0x00000000fae00000, 0x00000000fae00000)
the space 10240K, 40% used [0x00000000fa400000, 0x00000000fa800010, 0x00000000fa800200, 0x00000000fae00000)
compacting perm gen total 21248K, used 2973K [0x00000000fae00000, 0x00000000fc2c0000, 0x0000000100000000)
the space 21248K, 13% used [0x00000000fae00000, 0x00000000fb0e7428, 0x00000000fb0e7600, 0x00000000fc2c0000)
No shared spaces configured.
3.長期存活的對象進入老年代
既然虛擬機器採用了分代收集的思想來管理記憶體,那麼記憶體回收是就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。為了做到這點,虛擬機器給每個對象定義了一個對象年齡(Age)計數器。如果對象如果對象在Eden出生並經曆了一次Minor GC 後仍然存活,並且能被Survivor容納的話,將被移到Survivor空間中,並且年齡設為1.對象在Survivor中每熬過一次 Minor GC,對象的年齡加1歲,年齡加到一定的程度(預設是15),就會被晉陞到老年代中。
對象老年代的閾值可以可以通過參數-XX:MaxTenuringThreshold設定。
4.動態對象年齡判斷
為了更好的適應不同程式的記憶體狀況,虛擬機器並不是永遠的要求對象的年齡必須達到MaxTeburingThreshold才能晉陞為老年代,如果Survivor空間中相同年齡的所有對象大小的總和超多Survivor空間的一半,年齡大於或者等於該年齡的對象就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。
5. 空間分配擔保
在發生MInor GC 之前,虛擬機器會首先檢查老年代最大可用的連續空間是否大於新生代所有對象的總和,如過這個條件成立,那麼Minor GC 可以確保是安全的。如果不成立,則虛擬機器會查看HandelPromotionFailure設定值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代連續的可用的空間大小是否大於曆次晉陞到老年代對象的平均大小,如果大於,將嘗試一次Minor GC ,儘管這次Minor GC 是有風險的;如果小於或者HandelPromotionFailure設定不允許冒險,那這是也要進行一次Full GC。
-XX:-HandelPromotionFailure=false。
java記憶體配置策略