天天看點

記憶體配置設定政策這裡的記憶體指的是Java堆記憶體

這裡的記憶體指的是Java堆記憶體

Java堆記憶體的劃分:

1.jdk1.7及以前的劃分:

記憶體配置設定政策這裡的記憶體指的是Java堆記憶體

2.jdk1.8的記憶體劃分:

記憶體配置設定政策這裡的記憶體指的是Java堆記憶體
對象的記憶體配置設定,往大方向講,就是在堆上配置設定(但也可能經過JIT編譯後被拆散為标
量類型并間接地棧上配置設定[1]),對象主要配置設定在新生代的Eden區上,如果啟動了本地線程分
配緩沖,将按線程優先在TLAB上配置設定。 少數情況下也可能會直接配置設定在老年代中,配置設定的
規則并不是百分之百固定的,其細節取決于目前使用的是哪一種垃圾收集器組合,還有虛拟
機中與記憶體相關的參數的設定。
           
1.優先配置設定到Eden區
 2.如果有大對象,直接進入老年代
 3.長期存活的對象,配置設定到老年代
 4.如果Eden空間不夠,需要向老年代借空間,空間擔保
 5.動态對象年齡判斷
           
Java虛拟機中參數的定義
-Xms20M    設定Java堆記憶體最小為20M
-Xmx20M    設定Java堆記憶體最大為20M
-Xmn10M    設定Java堆記憶體新生代為10M
-XX:SurvivorRatio=8   新生代中Eden區與一個Survivor區的空間比例是8:1
-XX:PretenureSizeThreshold  令大于這個設定值的對象直接在老年代配置設定。 這樣做的目的是避免在Eden區及兩個Survivor區之間
							發生大量的記憶體複制
						
							 注意PretenureSizeThreshold參數隻對 Serial 和 ParNew 兩款收集器有效
							 
							 如果遇到必須使用此參數的場合,可以考慮 ParNew 加 CMS 的收集器組合
           
新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為Java對象大多都具備朝
					生夕滅的特性,是以Minor GC非常頻繁,一般回收速度也比較快。
					
老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴
							随至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集政策裡就有直接進行
							Major GC的政策選擇過程)。 Major GC的速度一般會比Minor GC慢10倍以上。
           

1.優先配置設定到Eden區

public class TestMemory {

    @Test
    public void test01() {
        byte[] bytes = new byte[8 * 1024 * 1024];
    }
}

           

由上面代碼測試,檢視垃圾收集日志 需要在運作的IDE設定VM options中添加以下參數:

記憶體配置設定政策這裡的記憶體指的是Java堆記憶體

可以檢視到日志如下:

記憶體配置設定政策這裡的記憶體指的是Java堆記憶體

可以得知,配置設定的8M空間,主要都是集中在Eden區,僅僅隻有一小部分配置設定在from survivor區域中。

當将代碼改造成如下代碼時:配置設定的記憶體空間變大些

2.如果有大對象,直接進入老年代

public class TestMemory {

    @Test
    public void test01() {
        byte[] bytes = new byte[40 * 1024 * 1024];
    }
}
           
記憶體配置設定政策這裡的記憶體指的是Java堆記憶體

可以看出,Eden區域裡面的記憶體空間占用反而變小了,但是ParOldGen裡面的 used占用變大了,說明在建立大記憶體的時候,會直接往老年代空間配置設定記憶體使用!

可以在VM options中指定參數,可以使你想讓的大對象進入老年代,   -XX:PretenureSizeThreshold
 這個參數來配置進入老年代的 對象的大小 
           
public class TestMemory {

    private  static int _1MB = 1024 * 1024;
    @Test
    public void test02() {
        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];
    }
 }
           

日志列印如下:

[GC (Allocation Failure) [DefNew: 8192K->1024K(9216K), 0.0099768 secs] 8192K->1117K(19456K), 0.0101741 secs] 
[Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 9216K->824K(9216K), 0.0032433 secs] 9309K->1857K(19456K), 0.0032779 secs] 
[Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 9016K->912K(9216K), 0.0108573 secs] 
10049K->2403K(19456K), 0.0108906 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) 
[DefNew: 7309K->195K(9216K), 0.0034662 secs] 8800K->6088K(19456K), 0.0035079 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 

Heap
 def new generation   total 9216K, used 7548K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  89% used [0x00000000fec00000, 0x00000000ff32e570, 0x00000000ff400000)
  from space 1024K,  19% used [0x00000000ff400000, 0x00000000ff430e40, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 5893K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  57% used [0x00000000ff600000, 0x00000000ffbc1510, 0x00000000ffbc1600, 0x0000000100000000)
 Metaspace       used 8590K, capacity 9494K, committed 9856K, reserved 1058816K
  class space    used 1104K, capacity 1289K, committed 1408K, reserved 1048576K
Disconnected from the target VM, address: '127.0.0.1:1089', transport: 'socket'


           

對以上現象的解釋:

test02()方法中,嘗試配置設定3個2MB大小和1個4MB大小的對象,
在運作時通過-Xms20M、 -Xmx20M、 -Xmn10M這3個參數限制了Java堆大小為20MB,不可擴
展,其中10MB配置設定給新生代,剩下的10MB配置設定給老年代。 -XX:SurvivorRatio=8決定了新
生代中Eden區與一個Survivor區的空間比例是8:1,從輸出的結果也可以清晰地看到“eden
space 8192K、 from space 1024K、 to space 1024K”的資訊,新生代總可用空間為
9216KB(Eden區+1個Survivor區的總容量)。

執行test02()中配置設定allocation4對象的語句時會發生一次Minor GC,這次GC的
結果是新生代6651KB變為148KB,而總記憶體占用量則幾乎沒有減少(因為allocation1、
allocation2、 allocation3三個對象都是存活的,虛拟機幾乎沒有找到可回收的對象)。 這次
GC發生的原因是給allocation4配置設定記憶體的時候,發現Eden已經被占用了6MB,剩餘空間已不
足以配置設定allocation4所需的4MB記憶體,是以發生Minor GC。 GC期間虛拟機又發現已有的3個
2MB大小的對象全部無法放入Survivor空間(Survivor空間隻有1MB大小),是以隻好通過分
配擔保機制提前轉移到老年代去。

這次GC結束後,4MB的allocation4對象順利配置設定在Eden中,是以程式執行完的結果是
Eden占用4MB(被allocation4占用),Survivor空閑,老年代被占用6MB(被allocation1、allocation2、 allocation3占用)。
           

3.長期存活的對象将進入老年代

既然虛拟機采用了分代收集的思想來管理記憶體,那麼記憶體回收時就必須能識别哪些對象
應放在新生代,哪些對象應放在老年代中。 為了做到這點,虛拟機給每個對象定義了一個對
象年齡(Age)計數器。 如果對象在Eden出生并經過第一次Minor GC後仍然存活,并且能被
Survivor容納的話,将被移動到Survivor空間中,并且對象年齡設為1。 對象在Survivor區中
每“熬過”一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(預設為15歲),就
将會被晉升到老年代中。 對象晉升老年代的年齡門檻值,可以通過參數-XX:MaxTenuringThreshold設定

當對象存活時間,大于等于  -XX:MaxTenuringThreshold 設定的參數值時,會将此對象放到老年代。否則此對象如果未被回收,
還是會在新年代空間中,等待回收或者是達到 參數值時 進入老年代空間。
           

4.動态對象年齡判定

為了能更好地适應不同程式的記憶體狀況,虛拟機并不是永遠地要求對象的年齡必須達到
了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總
和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等
到MaxTenuringThreshold中要求的年齡。
           

5.空間配置設定擔保

在發生Minor GC之前,虛拟機會先檢查老年代最大可用的連續空間是否大于新生代所有
對象總空間,如果這個條件成立,那麼Minor GC可以確定是安全的。 如果不成立,則虛拟機
會檢視HandlePromotionFailure設定值是否允許擔保失敗。 如果允許,那麼會繼續檢查老年代
最大可用的連續空間是否大于曆次晉升到老年代對象的平均大小,如果大于,将嘗試着進行
一次Minor GC,盡管這次Minor GC是有風險的;如果小于,或者HandlePromotionFailure設定
不允許冒險,那這時也要改為進行一次Full GC。

下面解釋一下“冒險”是冒了什麼風險,前面提到過,新生代使用複制收集算法,但為了
記憶體使用率,隻使用其中一個Survivor空間來作為輪換備份,是以當出現大量對象在Minor
GC後仍然存活的情況(最極端的情況就是記憶體回收後新生代中所有對象都存活),就需要
老年代進行配置設定擔保,把Survivor無法容納的對象直接進入老年代。 與生活中的貸款擔保類
似,老年代要進行這樣的擔保,前提是老年代本身還有容納這些對象的剩餘空間,一共有多
少對象會活下來在實際完成記憶體回收之前是無法明确知道的,是以隻好取之前每一次回收晉
升到老年代對象容量的平均大小值作為經驗值,與老年代的剩餘空間進行比較,決定是否進
行Full GC來讓老年代騰出更多空間。

取平均值進行比較其實仍然是一種動态機率的手段,也就是說,如果某次Minor GC存活
後的對象突增,遠遠高于平均值的話,依然會導緻擔保失敗(Handle Promotion Failure)。
如果出現了HandlePromotionFailure失敗,那就隻好在失敗後重新發起一次Full GC。 雖然擔保
失敗時繞的圈子是最大的,但大部分情況下都還是會将HandlePromotionFailure開關打開,避
免Full GC過于頻繁
           
-XX:-HandlePromotionFailure 參數是設定 是否允許擔保失敗

-XX:-HandlePromotionFailure = true   是允許擔保失敗
-XX:-HandlePromotionFailure = false  是不允許擔保失敗
           
在JDK 6 Update 24之後,HandlePromotionFailure參數不會再影響到虛拟機的空間配置設定擔保政策,
觀察OpenJDK中的源碼變化,雖然源碼中還定義了HandlePromotionFailure參數,但是在代碼中已經不會再使用它。
 JDK 6 Update 24之後的規則變為隻要老年代的連續空間大于新生代對象總大小或者曆次晉升的平均大小就
會進行Minor GC,否則将進行Full GC。 也就是 如果老年代 的空間 足夠新生代 對象使用 就會進行 新生代 垃圾回收,
如果 老年代 空間 不夠使用的, 就會先 進行Full GC (也就是先進性老年代空間垃圾回收,然後再新生代空間回收)
           
jvm