什麼是逃逸分析(Escape Analysis)?
在編程語言的編譯優化原理中,分析指針動態範圍的方法稱之爲逃逸分析。
通俗一點講,就是當一個對象的指針被多個方法或線程引用時,我們稱這個指針發生了逃逸。
而用來分析這種逃逸現象的方法,就稱之爲逃逸分析。
舉個例子:
Java代碼
class A {
publicstatic B b;
public void globalVariablePointerEscape(){// 給全局變量賦值,發生逃逸
b =new B();
}
public B methodPointerEscape(){// 方法返回值,發生逃逸
returnnew B();
}
public void instancePassPointerEscape(){
methodPointerEscape().printClassName(this);// 實例引用傳遞,發生逃逸
}
}
class B {
public void printClassName(A a){
System.out.println(a.class.getName());
}
}
在這個例子中,一共舉了3種常見的指針逃逸場景。分別是 全局變量賦值,方法返回值,實例引用傳遞。
逃逸分析優化JVM原理
我們知道java對象是在堆裏分配的,在調用棧中,只保存了對象的指針。
當對象不再使用後,需要依靠GC來遍歷引用樹並回收內存,如果對象數量較多,將給GC帶來較大壓力,也間接影響了應用的性能。減少臨時對象在堆內分配的數量,無疑是最有效的優化方法。
接下來,舉一個場景來闡述。
假設在方法體內,聲明瞭一個局部變量,且該變量在方法執行生命週期內未發生逃逸(在方法體內,未將引用暴露給外面)。
按照JVM內存分配機制,首先會在堆裏創建變量類的實例,然後將返回的對象指針壓入調用棧,繼續執行。
這是優化前,JVM的處理方式。
逃逸分析優化 – 棧上分配
優化原理:分析找到未逃逸的變量,將變量類的實例化內存直接在棧裏分配(無需進入堆),分配完成後,繼續在調用棧內執行,最後線程結束,棧空間被回收,局部變量也被回收。
這是優化後的處理方式,對比可以看出,主要區別在棧空間直接作爲臨時對象的存儲介質。從而減少了臨時對象在堆內的分配數量。
逃逸分析另一個重要的優化 – 鎖省略
如果通過逃逸分析能夠判斷出指向某個局部變量的多個引用被限制在同一方法體內,並且所有這些引用都不能“逃逸”到這個方法體以外的地方,那麼HotSpot會要求JIT執行一項優化動作 – 將局部變量上擁有的鎖省略掉。
這就是鎖省略(lock elision)。
性能測試
class DoubleSlot {
finalint value1;
finalint value2;
public DoubleSlot(int value1, int value2){
this.value1= value1;
this.value2= value2;
}
}
static int slotValue(DoubleSlot slot){
return slot.value1+ slot.value2;
}
static int sum(int[] values){
int sum =0;
int length = values.length;
for (int i=1; i<100; i++)
sum(values);
for (int i=0; i<100; i++)
test(values);
}
測試結果是:
$ /usr/jdk/jdk1.6.0_14/bin/java -server EscapeAnalysisTest
time 8889261
$ /usr/jdk/jdk1.6.0_14/bin/java -server -XX:+DoEscapeAnalysis?EscapeAnalysisTest
time 1408140
從結果中,可以看到,啓用逃逸分析的運行性能6倍於未啓用。
JVM中啓用逃逸分析 DoEscapeAnalysis
安裝jdk1.6.0_14,運行java時傳遞jvm參數 -XX:+DoEscapeAnalysis
出處:
http://kenwublog.com/jvm-optimization-escape-analysis