1. 程式人生 > >[JVM]記憶體分配策略

[JVM]記憶體分配策略

1、優先分配到eden

package 深入理解java虛擬機器;
 
public class 物件優先分配到eden區 {
 
	/**
	 * 1M的記憶體大小
	 */
	private static final int _1MB = 1024 * 1024;
 
	/**
	 * jvm引數設定:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
	 * 
	 * 1、列印垃圾回收日誌資訊:-verbose:gc -XX:+PrintGCDetails
	 * 2、設定jvm初始堆記憶體為20M:-Xms20M
	 * 3、設定jvm最大堆記憶體為20M:-Xmx20M
	 * 4、設定新生代堆記憶體大小為10M:-Xmn10M
	 * 5、設定新生代中Eden區與一個Survivor區的空間比例是8:1:-XX:SurvivorRatio=8
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	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
	}
	
	/**
	 * 執行結果如下:
	 * 
	 * Heap
	 *  PSYoungGen      total 9216K, used 7291K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
	 *  eden space 8192K, 89% used [0x00000000ff600000,0x00000000ffd1ef00,0x00000000ffe00000)
	 *  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
	 *  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) 
	 *  ParOldGen       total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
	 *  object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000)
	 *  Metaspace       used 2669K, capacity 4486K, committed 4864K, reserved 1056768K
	 *  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
	 * 
	 * PSYoungGen表示當前所使用的垃圾收集器是Parallel,可使用引數-XX:+UseSerialGC 來指定jvm使用Serial
	 * 
	 * 使用Serial收集器的執行結果如下:
	 * 
	 * [GC (Allocation Failure) [DefNew: 7127K->522K(9216K), 0.0033855 secs] 7127K->6666K(19456K), 0.0034315 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
	 * Heap
	 *  def new generation   total 9216K, used 4701K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
	 *  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
	 *  from space 1024K,  51% used [0x00000000ff500000, 0x00000000ff582ad0, 0x00000000ff600000)
	 *  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
	 *  tenured generation   total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
	 *  the space 10240K,  60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
	 *  Metaspace       used 2669K, capacity 4486K, committed 4864K, reserved 1056768K
	 *  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
	 * 
	 * 可見,Serial收集器使用的是def new generation來標識新生代的
	 * 新生代的總記憶體是10M,eden區佔8M,兩個Survivor區各佔1M
	 * 在分配allocation1到allocation3的時候,eden區的空間是夠用的,此時eden區佔用了6M,剩餘2M
	 * 當分配allocation4的時候,由於allocation4需要4M的記憶體,此時eden區的空間已經不夠用了,所以jvm就觸發了一次新生代的垃圾回收
	 * 新生代的垃圾回收使用了複製演算法,是將新生代中的所有存活的物件移動到Survivor中。
	 * 由於Survivor區只有1M的記憶體,很明顯三個物件都放不下,此時就必須觸發記憶體擔保機制了,即將三個物件移動到老年代中。
	 * 此時eden區就是出於空閒狀態了,所以allocation4就成功的被分配到了eden區中。
	 * 所以我們看到的結果就是最終新生代使用了4M記憶體,而老年代使用了6M記憶體,而從另一方面也可以看出物件建立時是優先分配到eden區中的。
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public static void main(String[] args) {
		testAllocation();
	}
}

2、大物件直接分配到老年代

public class 大物件直接分配到老年代 {
 
	private static final int _1MB = 1024 * 1024;
 
	/**
	 * jvm引數設定:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
	 * -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
	 * 
	 * 1、指定記憶體佔用大於3145728的物件直接分配到老年代:-XX:PretenureSizeThreshold=3145728
	 * (3145728=3M=3*1024*1024,超過3M的物件會被直接放到老年代中)
	 * 
	 * 2、PretenureSizeThreshold只針對Serial和ParNew有效
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public static void testPretenureSizeThreshold() {
		byte[] allocation;
		allocation = new byte[4 * _1MB]; // 直接分配在老年代中
	}
 
}

3、長期存活的物件分配到老年代

public class 長期存活的物件分配到老年代 {
 
	private static final int _1MB = 1024 * 1024;
 
	/**
	 * jvm引數設定:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
	 * -XX:SurvivorRatio=8 -XX:+PrintTenuringDistribution
	 * -XX:MaxTenuringThreshold=1
	 * 
	 * 1、物件在新生代中每經歷過一次Minor GC,若物件進入存活區的話,則將物件的年齡都會加1,當物件的年齡到達某一個值時,物件就會進入老年代
	 * 
	 * 2、設定進入老年代年齡的閾值為1:-XX:MaxTenuringThreshold=1
	 * 
	 *@author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	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];
	}
 
}

4、動態物件年齡判斷

  • 為了更好的適應不同程式的記憶體狀況,虛擬機器並不是永遠地要求物件的年齡必須到達了MaxTenuringThreshold才能晉升到老年代。

  • 如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡。

5、空間分配擔保(eden區記憶體不夠,到老年代那去借)

public class 空間分配擔保 {
 
	private static final int _1MB = 1024 * 1024;
 
	/**
	 * jvm引數設定:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
	 * -XX:-HandlePromotionFailure
	 * 
	 * 1、此設定中,eden區有8M,兩個Survivor區各佔1M,老年代佔10M
	 * 2、每次進行Minor GC之前,虛擬機器會先檢查老年代的最大可用的連續空間是否大於新生代所有物件的空間
	 * 3、如果是的話,則Minor GC是安全的
	 * 4、如果不是的話,則虛擬機器先看HandlePromotionFailure的值,如果值設定成-的話,則虛擬機器需要啟動一次Full GC讓老年代騰出更多的空間
	 * 5、如果HandlePromotionFailure的值設定成+的話,則會檢查老年代中的最大可用連續空間是否大於歷次晉升到老年代物件的平均大小
	 * 6、如果不是的話,則會進行一次Full GC,如果是的話,虛擬機器則會嘗試一次Minor GC,儘管它是有風險的
	 * 7、如果Minor GC失敗了,則會重新發起一次Full GC
	 * 8、雖然擔保失敗時繞的圈子是最大的,但大多數情況下還是會將HandlePromotionFailure開啟,避免Full GC過於頻繁
	 * 
	 * 注:HandlePromotionFailure引數只在JDK6之前有效,JDK6之後該值不可變且預設為開啟。
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	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];
	}
}

6、逃逸分析與棧上分配

public class 逃逸分析與棧上分配 {
 
	public String str;
	
	/**
	 * 方法返回str,物件發生了逃逸 
	 * 
	 *@author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public String getStr() {
		return str == null ? new String() : str;
	}
	
	/**
	 * 為成員屬性賦值,發生逃逸
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public void setStr() {
		this.str = new String();
	}
	
	/**
	 * 物件的作用域僅在當前方法中有效,沒有發生逃逸(
	 * 
	 * (只要物件不發生逃逸,那麼就會把物件分配到棧記憶體中去。物件隨棧幀的消失而消失)
	 * 
	 * @author huangxj 2018年1月21日 
	 *
	 * @version v1.0
	 */
	public void useStr() {
		String str = new String();
	}
	
	/**
	 * 引用成員變數的值,發生逃逸
	 * 
	 * @author jinqiwen 2018年10月2日 
	 *
	 * @version v1.0
	 */
	public void useStr2() {
		String str = getStr();
	}
}