逃逸分析
在編程語言的編譯優(yōu)化原理中素跺,分析指針動態(tài)范圍的方法稱之為逃逸分析齐苛。通俗來講,當(dāng)一個對象的指針被多個方法或線程引用時胸嘁,我們稱這個指針發(fā)生了逃逸瓶摆。
常見的逃逸場景:全局變量賦值、方法返回值性宏、實例引用傳遞
public class A {
public static B b;
//給全局變量賦值群井,發(fā)生逃逸
public void globalVariablePointerEscape(){
b = new B();
}
//方法返回值,發(fā)生逃逸
public B methodPointerEscape(){
return new B();
}
//實例引用傳遞毫胜,發(fā)生逃逸
public void instancePassPointerEscape(){
methodPointerEscape().printClassName(this);
}
}
public class B {
public void printClassName(A a){
System.out.println(a.getClass().getName());
}
}
逃逸分析原理
我們知道Java對象是在堆里分配的书斜,在調(diào)用棧中,只保存了對象的指針酵使。當(dāng)對象不再使用后荐吉,需要依靠GC來遍歷引用樹并回收內(nèi)存,如果對象數(shù)量較多口渔,將給GC帶來較大壓力稍坯。因此,減少臨時對象在堆內(nèi)存分配的數(shù)量是最有效的優(yōu)化方法。
場景應(yīng)用一:棧上分配
其實瞧哟,在java應(yīng)用里普遍存在一種場景混巧。一般是在方法體內(nèi),聲明了一個局部變量勤揩,且該變量在方法執(zhí)行生命周期內(nèi)未發(fā)生逃逸(在方法體內(nèi)咧党,未將引用暴露給外面)。按照J(rèn)VM內(nèi)存分配機(jī)制陨亡,首先會在堆里創(chuàng)建變量類的實例傍衡,然后將返回的對象指針壓入調(diào)用棧,繼續(xù)執(zhí)行负蠕。這是優(yōu)化前蛙埂,JVM的處理方式。
- 逃逸分析優(yōu)化 - 棧上分配
分析找到未逃逸的變量遮糖,將變量類的實例化內(nèi)存直接在棧里分配(無需進(jìn)入堆)绣的,分配完成后,繼續(xù)在調(diào)用棧內(nèi)執(zhí)行欲账,最后線程結(jié)束屡江,棧空間被回收赛不,局部變量對象也被回收惩嘉。對比可以看出,主要區(qū)別在椞吖剩空間直接作為臨時對象的存儲介質(zhì)文黎。從而減少了臨時對象在堆內(nèi)的分配數(shù)量。
應(yīng)用場景二:同步消除
在即使編譯器時殿较,如果發(fā)現(xiàn)不可能被共享的對象臊诊,則可以消除這些對象的鎖操作。
也許你會覺得奇怪斜脂,既然有些對象不可能被多線程訪問,那為什么要加鎖呢触机?寫代碼時直接不加鎖不就好了帚戳。但是有時,這些鎖并不是程序員所寫的儡首,有的是JDK實現(xiàn)中就有鎖的片任,比如Vector和StringBuffer這樣的類,它們中的很多方法都是有鎖的蔬胯。當(dāng)我們在一些不會有線程安全的情況下使用這些類的方法時对供,達(dá)到某些條件時,編譯器會將鎖消除來提高性能。
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可以判斷鹅髓,如果這個局部變量StringBuffer并沒有逃出它的作用域,那么可以確定這個StringBuffer并不會被多線程所訪問京景,那么就可以把這些多余的鎖給去掉來提高性能窿冯。
應(yīng)用場景三:標(biāo)量替換
Java虛擬機(jī)中的原始數(shù)據(jù)類型(int,long等數(shù)值類型以及reference類型等)都不能再進(jìn)一步分解确徙,它們就可以稱為標(biāo)量醒串。相對的,如果一個數(shù)據(jù)可以繼續(xù)分解鄙皇,那它稱為聚合量芜赌,Java中最典型的聚合量是對象。如果逃逸分析證明一個對象不會被外部訪問伴逸,并且這個對象是可分解的缠沈,那程序真正執(zhí)行的時候?qū)⒖赡懿粍?chuàng)建這個對象,而改為直接創(chuàng)建它的若干個被這個方法使用到的成員變量來代替违柏。拆散后的變量便可以被單獨分析與優(yōu)化博烂,可以各自分別在棧幀或寄存器上分配空間,原本的對象就無需整體分配空間了漱竖。
- 轉(zhuǎn)載自
JVM中的逃逸分析一
JVM中的逃逸分析二