1. 一般狀況下,對象在eden中分配。當eden沒法分配時,觸發一次Minor GC。 java
public class YoungGenGc { private static final int _1MB = 1024 * 1024; public static void main( String[] args ) { testAllocation(); } /** * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 */ public static void testAllocation() { 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 ];//出現一次 Minor GC } }
testAllocation()方法輸出結果 算法
[GC [DefNew: 6651K->148K(9216K), 0.0070106 secs] 6651K->6292K(19456K), 0.0070426 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4326K [0x029d0000, 0x033d0000, 0x033d0000) eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000) from space 1024K, 14% used [0x032d0000, 0x032f5370, 0x033d0000) to space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000) tenured generation total 10240K, used 6144K [0x033d0000, 0x03dd0000, 0x03dd0000) the space 10240K, 60% used [0x033d0000, 0x039d0030, 0x039d0200, 0x03dd0000) compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000) the space 12288K, 17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000) No shared spaces configured.
執行testAllocation()方法後輸出了GC日誌以及內存分配情況。-Xms20M -Xmx20M -Xmn10M這3個參數肯定了Java堆大小爲20M,不可擴展,其中10M分配給新生代,剩下的10M即爲老年代。-XX:SurvivorRatio=8決定了新生代中eden與survivor的空間比例是1:8,從輸出的結果也清晰的看到「eden space 8192K、from space 1024K、to space 1024K」的信息,新生代總可用空間爲9216K(eden+1個survivor)。 spa
咱們也注意到在執行testAllocation()時出現了一次Minor GC,GC的結果是新生代6651K變爲148K,而總佔用內存則幾乎沒有減小(由於幾乎沒有可回收的對象)。此次GC是發生的緣由是爲allocation4分配內存的時候,eden已經被佔用了6M,剩餘空間已不足分配allocation4所需的4M內存,所以發生Minor GC。GC期間虛擬機發現已有的3個2M大小的對象所有沒法放入survivor空間(survivor空間只有1M大小),因此直接轉移到老年代去。GC後4M的allocation4對象分配在eden中。
2. 配置了PretenureSizeThreshold的狀況下,對象大於設置值將直接在老年代分配 3d
/** * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 * -XX:PretenureSizeThreshold=3145728 */ public static void testPretenureSizeThreshold() { byte[] allocation; allocation = new byte[ 4 * _1MB ];//直接分配在老年代中 }
testPretenureSizeThreshold()方法輸出: 日誌
Heap def new generation total 9216K, used 671K [0x029d0000, 0x033d0000, 0x033d0000) eden space 8192K, 8% used [0x029d0000, 0x02a77e98, 0x031d0000) from space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000) to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000) tenured generation total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000) the space 10240K, 40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000) compacting perm gen total 12288K, used 2107K [0x03dd0000, 0x049d0000, 0x07dd0000) the space 12288K, 17% used [0x03dd0000, 0x03fdefd0, 0x03fdf000, 0x049d0000) No shared spaces configured.
執行testPretenureSizeThreshold()方法後,咱們看到eden空間幾乎沒有被使用,而老年代的10M控件被使用了40%,也就是4M的allocation對象直接就分配在老年代中,則是由於PretenureSizeThreshold被設置爲3M,所以超過3M的對象都會直接從老年代分配。 code
3.在eden通過GC後存活,而且survivor能容納的對象,將移動到survivor空間內,若是對象在survivor中繼續熬過若干次回收(默認爲15次)將會被移動到老年代中。回收次數由MaxTenuringThreshold設置。
分別以-XX:MaxTenuringThreshold=1和-XX:MaxTenuringThreshold=15兩種設置來執行testTenuringThreshold(),方法中allocation1對象須要256K內存,survivor空間能夠容納。當MaxTenuringThreshold=1時,allocation1對象在第二次GC發生時進入老年代,新生代已使用的內存GC後很是乾淨的變成0KB。而MaxTenuringThreshold=15時,第二次GC發生後,allocation1對象則還留在新生代survivor空間,這時候新生代仍然有404KB被佔用。 對象
/** * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 * -XX:+PrintTenuringDistribution */ @SuppressWarnings( "unused" ) public static void testTenuringThreshold() { byte[] allocation1, allocation2, allocation3; allocation1 = new byte[ _1MB / 4 ]; // 什麼時候進入老年代決定於XX:MaxTenuringThreshold設置 allocation2 = new byte[ 4 * _1MB ]; allocation3 = new byte[ 4 * _1MB ]; allocation3 = null; allocation3 = new byte[ 4 * _1MB ]; }
輸出MaxTenuringThreshold=1 內存
[GC [DefNew Desired survivor size 524288 bytes, new threshold 1 (max 1) - age 1: 414664 bytes, 414664 total : 4859K->404K(9216K), 0.0065012 secs] 4859K->4500K(19456K), 0.0065283 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] [GC [DefNew Desired survivor size 524288 bytes, new threshold 1 (max 1) : 4500K->0K(9216K), 0.0009253 secs] 8596K->4500K(19456K), 0.0009458 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000) eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000) from space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000) to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000) tenured generation total 10240K, used 4500K [0x033d0000, 0x03dd0000, 0x03dd0000) the space 10240K, 43% used [0x033d0000, 0x03835348, 0x03835400, 0x03dd0000) compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000) the space 12288K, 17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)
MaxTenuringThreshold=1 5 虛擬機
[GC [DefNew Desired survivor size 524288 bytes, new threshold 15 (max 15) - age 1: 414664 bytes, 414664 total : 4859K->404K(9216K), 0.0049637 secs] 4859K->4500K(19456K), 0.0049932 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC [DefNew Desired survivor size 524288 bytes, new threshold 15 (max 15) - age 2: 414520 bytes, 414520 total : 4500K->404K(9216K), 0.0008091 secs] 8596K->4500K(19456K), 0.0008305 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4582K [0x029d0000, 0x033d0000, 0x033d0000) eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000) from space 1024K, 39% used [0x031d0000, 0x03235338, 0x032d0000) to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000) tenured generation total 10240K, used 4096K [0x033d0000, 0x03dd0000, 0x03dd0000) the space 10240K, 40% used [0x033d0000, 0x037d0010, 0x037d0200, 0x03dd0000) compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000) the space 12288K, 17% used [0x03dd0000, 0x03fe0998, 0x03fe0a00, 0x049d0000)
4. 若是在survivor空間中相同年齡全部對象大小的累計值大於survivor空間的一半,大於或等於各年齡的對象就能夠直接進入老年代,無需達到MaxTenuringThreshold中要求的年齡。 io
/** * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 * -XX:+PrintTenuringDistribution */ public static void testTenuringThreshold2() { byte[] allocation1, allocation2, allocation3, allocation4; allocation1 = new byte[ _1MB / 4 ]; // allocation1+allocation2大於survivo空間一半 allocation2 = new byte[ _1MB / 4 ]; allocation3 = new byte[ 4 * _1MB ]; allocation4 = new byte[ 4 * _1MB ]; allocation4 = null; allocation4 = new byte[ 4 * _1MB ]; }
testTenuringThreshold2() 輸出
[GC [DefNew Desired survivor size 524288 bytes, new threshold 1 (max 15) - age 1: 676824 bytes, 676824 total : 5115K->660K(9216K), 0.0050136 secs] 5115K->4756K(19456K), 0.0050443 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] [GC [DefNew Desired survivor size 524288 bytes, new threshold 15 (max 15) : 4756K->0K(9216K), 0.0010571 secs] 8852K->4756K(19456K), 0.0011009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4178K [0x029d0000, 0x033d0000, 0x033d0000) eden space 8192K, 51% used [0x029d0000, 0x02de4828, 0x031d0000) from space 1024K, 0% used [0x031d0000, 0x031d0000, 0x032d0000) to space 1024K, 0% used [0x032d0000, 0x032d0000, 0x033d0000) tenured generation total 10240K, used 4756K [0x033d0000, 0x03dd0000, 0x03dd0000) the space 10240K, 46% used [0x033d0000, 0x038753e8, 0x03875400, 0x03dd0000) compacting perm gen total 12288K, used 2114K [0x03dd0000, 0x049d0000, 0x07dd0000) the space 12288K, 17% used [0x03dd0000, 0x03fe09a0, 0x03fe0a00, 0x049d0000) No shared spaces configured.
執行testTenuringThreshold2()方法,並將設置-XX:MaxTenuringThreshold=15,發現運行結果中survivor佔用仍然爲0%,而老年代比預期增長了6%,也就是說allocation一、allocation2對象都直接進入了老年代,而沒有等待到15歲的臨界年齡。由於這2個對象加起來已經到達了512K,而且它們是同年的,知足同年對象達到survivor空間的一半規則。咱們只要註釋掉其中一個對象new操做,就會發現另一個就不會晉升到老年代中去了。
5. 在Minor GC觸發時,會檢測以前每次晉升到老年代的平均大小是否大於老年代的剩餘空間,若是大於,改成直接進行一次Full GC,若是小於則查看HandlePromotionFailure設置看看是否容許擔保失敗,若是容許,那仍然進行Minor GC,若是不容許,則也要改成進行一次Full GC。
/** * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure */ public static void testHandlePromotion() { byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7; allocation1 = new byte[ 2 * _1MB ]; allocation2 = new byte[ 2 * _1MB ]; allocation3 = new byte[ 2 * _1MB ]; allocation1 = null; allocation4 = new byte[ 2 * _1MB ]; allocation5 = new byte[ 2 * _1MB ]; allocation6 = new byte[ 2 * _1MB ]; allocation4 = null; allocation5 = null; allocation6 = null; allocation7 = new byte[ 2 * _1MB ]; }
testHandlePromotion() 輸出:
HandlePromotionFailure = false [GC [DefNew: 6651K->148K(9216K), 0.0078936 secs] 6651K->4244K(19456K), 0.0079192 secs] [Times: user=0.00 sys=0.02, real=0.02 secs] [GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs][Tenured: 4096K->4244K(10240K), 0.0042901 secs] 10474K->4244K(19456K), [Perm : 2104K->2104K(12288K)], 0.0043613 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] HandlePromotionFailure = true [GC [DefNew: 6651K->148K(9216K), 0.0054913 secs] 6651K->4244K(19456K), 0.0055327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC [DefNew: 6378K->148K(9216K), 0.0006584 secs] 10474K->4244K(19456K), 0.0006857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
前面提到,新生代纔有複製收集算法,但爲了內存利用率,只使用其中一個survivor空間來做爲輪換備份,所以當出現大量對象在GC後仍然存活的狀況(最極端就是GC後全部對象都存活),就須要老年代進行分配擔保,把survivor沒法容納的對象直接放入老年代。與生活中貸款擔保相似,老年代要進行這樣的擔保,前提就是老年代自己還有容納這些對象的剩餘空間,一共有多少對象在GC以前是沒法明確知道的,因此取以前每一次GC晉升到老年代對象容量的平均值與老年代的剩餘空間進行比較決定是否進行Full GC來讓老年代騰出更多空間。
取平均值進行比較其實仍然是一種動態機率的手段,也就是說若是某次Minor GC存活後的對象突增,大大高於平均值的話,依然會致使擔保失敗,這樣就只好在失敗後從新進行一次Full GC。雖然擔保失敗時作的繞的圈子是最大的,但大部分狀況下都仍是會將HandlePromotionFailure打開,避免Full GC過於頻繁。