什麼是逃逸分析?
在很早以前,Java代碼從編寫完畢到JVM執行至少需要兩個過程:
- javac將Java代碼編譯成字節碼class文件。
- 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大法了。