Java逃逸分析之棧上分配內存

什麼是逃逸分析?

在很早以前,Java代碼從編寫完畢到JVM執行至少需要兩個過程:

  1. javac將Java代碼編譯成字節碼class文件。
  2. JVM載入class文件後,由解釋器來逐條將字節碼指令解釋翻譯成本地機器碼並執行。

因此,Java也被稱爲是一門”解釋執行“的語言,由於解釋執行比編譯執行要慢,所以”Java程序很慢“在早期深入人心。
爲了解決“解釋執行”的效率問題,Java引入了JIT即時編譯器,當JVM發現某段代碼塊運行的特別頻繁時就會將這部分熱點代碼進行編譯、優化並緩存,以便下次直接使用。

“逃逸分析技術”就是Java用來提升效率的技術手段之一,它通過動態的分析對象的作用域,來判斷一個對象是否發生逃逸,如果沒有逃逸,那麼JVM可以做出以下優化:

  • 鎖消除
    既然沒有發生逃逸,位於線程私有的棧中天生線程安全,對數據的讀寫就無需加鎖。
  • 標量替換
    如果可以,會對聚合量進行拆分,直接使用標量進行替換。(無法拆分,基本數據類型)
  • 棧上分配
    直接在方法棧中分配內存,方法出棧後就被銷燬,減輕GC壓力。

從JDK7開始,逃逸分析默認開啓,也可通過參數-XX:[+|-]DoEscapeAnalysis自由設置。

棧上分配內存

“幾乎”所有的對象都在堆中分配內存,方法棧中只保存基本數據類型以及對象引用。
不過隨着逃逸分析技術的成熟,如果JVM發現對象沒有發生逃逸,那麼會改變這個內存分配策略,優先考慮在棧上分配內存,帶來的好處是:方法出棧後內存就被銷燬,無需GC額外回收。
GC回收時是需要暫停用戶線程的,雖然現在的垃圾回收器已經將STW的的時間儘可能的減少了,但是不管怎麼說,只要能減輕GC的壓力就能進一步提高系統的吞吐量。

以上都是概念,下面通過幾段示例代碼實戰一下開啓逃逸分析後都帶來了哪些優化。

public class EscapeAnalysis {

	static class MyClass {
		String name;
	}

	/**
	 * VM Args: -Xmx4G -Xms4G -XX:[-|+]DoEscapeAnalysis -XX:+PrintGCDetails
	 */
	public static void main(String[] args) throws Exception {
		long t1 = System.currentTimeMillis();
		for (int i = 0; i < 100000000; i++) {
			create();
		}
		long t2 = System.currentTimeMillis();
		System.out.println(t2 - t1);
	}

	static void create() {
		MyClass myClass = new MyClass();
		myClass.name = "";//沒有逃逸
	}

	static MyClass createEscape() {
		MyClass myClass = new MyClass();
		myClass.name = "";
		return myClass;//發生逃逸,myClass可能會被其他線程訪問
	}
}

性能上
構建1億個對象實例,關閉逃逸分析耗時1018ms,開啓逃逸分析後耗時8ms。

內存上
爲了避免發生GC,只創建一千萬個對象實例,打開GC日誌,結果如下:
在這裏插入圖片描述
在這裏插入圖片描述
如果說以上數據還不能證明對象確實被分配在了棧上,那麼只好祭出jmap大法了。
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章