逃逸分析
在編程語(yǔ)言的編譯優(yōu)化原理中,分析指針動(dòng)態(tài)范圍的方法稱之為逃逸分析。通俗來(lái)講,當(dāng)一個(gè)對(duì)象的指針被多個(gè)方法或線程引用時(shí),我們稱這個(gè)指針發(fā)生了逃逸。
常見(jiàn)的逃逸場(chǎng)景:全局變量賦值、方法返回值、實(shí)例引用傳遞
public class A {
public static B b;
//給全局變量賦值,發(fā)生逃逸
public void globalVariablePointerEscape(){
b = new B();
}
//方法返回值,發(fā)生逃逸
public B methodPointerEscape(){
return new B();
}
//實(shí)例引用傳遞,發(fā)生逃逸
public void instancePassPointerEscape(){
methodPointerEscape().printClassName(this);
}
}
public class B {
public void printClassName(A a){
System.out.println(a.getClass().getName());
}
}
逃逸分析原理
我們知道Java對(duì)象是在堆里分配的,在調(diào)用棧中,只保存了對(duì)象的指針。當(dāng)對(duì)象不再使用后,需要依靠GC來(lái)遍歷引用樹(shù)并回收內(nèi)存,如果對(duì)象數(shù)量較多,將給GC帶來(lái)較大壓力。因此,減少臨時(shí)對(duì)象在堆內(nèi)存分配的數(shù)量是最有效的優(yōu)化方法。
場(chǎng)景應(yīng)用一:棧上分配
其實(shí),在java應(yīng)用里普遍存在一種場(chǎng)景。一般是在方法體內(nèi),聲明了一個(gè)局部變量,且該變量在方法執(zhí)行生命周期內(nèi)未發(fā)生逃逸(在方法體內(nèi),未將引用暴露給外面)。按照J(rèn)VM內(nèi)存分配機(jī)制,首先會(huì)在堆里創(chuàng)建變量類的實(shí)例,然后將返回的對(duì)象指針壓入調(diào)用棧,繼續(xù)執(zhí)行。這是優(yōu)化前,JVM的處理方式。
- 逃逸分析優(yōu)化 - 棧上分配
分析找到未逃逸的變量,將變量類的實(shí)例化內(nèi)存直接在棧里分配(無(wú)需進(jìn)入堆),分配完成后,繼續(xù)在調(diào)用棧內(nèi)執(zhí)行,最后線程結(jié)束,棧空間被回收,局部變量對(duì)象也被回收。對(duì)比可以看出,主要區(qū)別在??臻g直接作為臨時(shí)對(duì)象的存儲(chǔ)介質(zhì)。從而減少了臨時(shí)對(duì)象在堆內(nèi)的分配數(shù)量。
應(yīng)用場(chǎng)景二:同步消除
在即使編譯器時(shí),如果發(fā)現(xiàn)不可能被共享的對(duì)象,則可以消除這些對(duì)象的鎖操作。
也許你會(huì)覺(jué)得奇怪,既然有些對(duì)象不可能被多線程訪問(wèn),那為什么要加鎖呢?寫(xiě)代碼時(shí)直接不加鎖不就好了。但是有時(shí),這些鎖并不是程序員所寫(xiě)的,有的是JDK實(shí)現(xiàn)中就有鎖的,比如Vector和StringBuffer這樣的類,它們中的很多方法都是有鎖的。當(dāng)我們?cè)谝恍┎粫?huì)有線程安全的情況下使用這些類的方法時(shí),達(dá)到某些條件時(shí),編譯器會(huì)將鎖消除來(lái)提高性能。
public class BufferTest {
public static void main(String[] args){
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i ++){
createStringBuffer("JVM", "EscapeAnalysis");
}
long end = System.currentTimeMillis();
System.out.println("it takes " + (end - start) + " ms");
}
public static String createStringBuffer(String s1, String s2){
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
}
優(yōu)化前:
it takes 867 ms
優(yōu)化后:
it takes 802 ms
基于逃逸分析,JVM可以判斷,如果這個(gè)局部變量StringBuffer并沒(méi)有逃出它的作用域,那么可以確定這個(gè)StringBuffer并不會(huì)被多線程所訪問(wèn),那么就可以把這些多余的鎖給去掉來(lái)提高性能。
應(yīng)用場(chǎng)景三:標(biāo)量替換
Java虛擬機(jī)中的原始數(shù)據(jù)類型(int,long等數(shù)值類型以及reference類型等)都不能再進(jìn)一步分解,它們就可以稱為標(biāo)量。相對(duì)的,如果一個(gè)數(shù)據(jù)可以繼續(xù)分解,那它稱為聚合量,Java中最典型的聚合量是對(duì)象。如果逃逸分析證明一個(gè)對(duì)象不會(huì)被外部訪問(wèn),并且這個(gè)對(duì)象是可分解的,那程序真正執(zhí)行的時(shí)候?qū)⒖赡懿粍?chuàng)建這個(gè)對(duì)象,而改為直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來(lái)代替。拆散后的變量便可以被單獨(dú)分析與優(yōu)化,可以各自分別在棧幀或寄存器上分配空間,原本的對(duì)象就無(wú)需整體分配空間了。
- 轉(zhuǎn)載自
JVM中的逃逸分析一
JVM中的逃逸分析二