new出來的對象,不一定在堆上?聊聊Java虛擬機(jī)的優(yōu)化技術(shù):逃逸分析
逃逸分析(Escape Analysis)是一種靜態(tài)程序分析技術(shù),主要用于判定對象的可見范圍(Visibility)與生命周期(Lifetime)。該技術(shù)是現(xiàn)代即時編譯器實(shí)現(xiàn)局部化優(yōu)化、提升內(nèi)存使用效率、降低同步成本的基礎(chǔ)。
通俗來說,逃逸分析的核心在于回答這樣一個問題:某個對象是否可能“逃逸”出它所創(chuàng)建的方法或線程作用域?
逃逸分析的結(jié)果通常分為三種情形。
1)未逃逸(No Escape):對象完全局限在當(dāng)前方法內(nèi),既未作為返回值,也未傳遞到其他線程或方法。
2)方法逃逸(Method Escape):對象作為參數(shù)傳遞到其他方法中,雖然不一定跨線程訪問,但由于編譯器無法確定外部方法的副作用,因此仍視為潛在逃逸。
3)線程逃逸(Thread Escape):對象的引用被賦值給共享變量,或作為任務(wù)傳遞給其他線程。這類對象無法進(jìn)行逃逸相關(guān)優(yōu)化,必須保留其線程安全保障。
下面代碼的是對象未逃逸的例子:
// add方法中創(chuàng)建了一個名為NonEscapeObject的對象。
// 這個對象僅在add方法中使用,用于計算兩個整數(shù)的和。
// 這個對象沒有作為方法的返回值、賦值給全局變量或作為參數(shù)傳遞給其他方法。
// 因此它被認(rèn)為是未逃逸的。
int add(int a, int b) {
NonEscapeObject o = new NonEscapeObject(a, b);
return obj.getX() + obj.getY();
}
class NonEscapeObject {
private int x;
private int y;
}
基于逃逸分析的信息,即時編譯器可以執(zhí)行一些優(yōu)化,例如同步鎖消除(Synchronization Elimination)、標(biāo)量替換(Scalar Replacement)和棧上分配(Stack allcotion)。
同步鎖消除
線程同步是一個相對耗時的過程,如果逃逸分析能確定一個共享變量不會逃出線程,無法被其他線程訪問,那這個共享變量的讀寫肯定就不會有競爭,對這個變量實(shí)施的同步措施也就可以消除掉。
// 由于obj沒有逃逸出doSomething()方法的范圍,編譯器可以進(jìn)行逃逸分析并確定該對象不會被其他線程訪問。
// 在逃逸分析確定obj對象不會逃逸的情況下,編譯器可以消除對該對象的同步鎖操作。
void doSomething() {
Object obj = new Object();
synchronized (obj) {
// 對obj進(jìn)行一些操作
// ...
}
}
標(biāo)量替換
標(biāo)量(scalar)是指一個無法再分解成更小的數(shù)據(jù)的數(shù)據(jù)。Java 中的基本數(shù)據(jù)類型就是標(biāo)量。相對的Java 中的對象就是聚合量(Aggregate),因?yàn)樗梢苑纸獬善渌酆狭亢蜆?biāo)量。
如果經(jīng)過逃逸分析,發(fā)現(xiàn)一個對象并沒有逃逸出方法和線程,那么就可以將這個對象視為一組標(biāo)量值。這樣,Java虛擬機(jī)就可以將這個對象的所有字段視為局部變量,從而在棧上分配這些局部變量,而不是在堆上分配整個對象,這樣可以減少堆內(nèi)存的占用。
void test() {
Point point = new Point(1,2);
System.out.println("point.x" + point.x + ";point.y" + point.y);
}
class Point {
private int x;
private int y;
}
假設(shè)有一個Point對象,包含x和y兩個字段。如果經(jīng)過逃逸分析,發(fā)現(xiàn)這個Point對象并沒有逃逸出方法,那么Java虛擬機(jī)就可以將這個Point對象視為兩個獨(dú)立的標(biāo)量值x和y,然后在棧上分配這兩個值,而不是在堆上分配整個Point對象。
void test() {
int x = 1;
int y = 2;
System.out.println("point.x = " + x + "; point.y=" + y);
}
棧上分配
Java的對象是在堆上分配的,Java虛擬機(jī)對堆內(nèi)存的垃圾對象回收是一個耗時的過程。在一般應(yīng)用中,不會逃逸的局部對象所占的比例很大,如果能使用棧上分配,那大量的對象就會隨著方法的結(jié)束而自動銷毀,垃圾收集系統(tǒng)的壓力將會小很多。
雖然逃逸分析理論上支持將非逃逸對象直接分配到棧上,從而避免堆內(nèi)存開銷與垃圾回收成本,但如HotSpot虛擬機(jī)并未真正實(shí)現(xiàn)物理意義上的棧上分配。原因在于:在支持線程搶占、嵌套調(diào)用、異?;謴?fù)與棧幀遷移(如逃逸到堆)等復(fù)雜運(yùn)行時語義的情況下,棧上對象生命周期管理的正確性將變得異常困難,容易引發(fā)并發(fā)可見性等問題。因此,Hotspot虛擬機(jī)并沒有進(jìn)行實(shí)際的棧上分配,而是使用了標(biāo)量替換這一技術(shù)。
盡管逃逸分析為即時編譯器帶來了多種激進(jìn)優(yōu)化的可能,但它本身也是一項計算復(fù)雜度較高的靜態(tài)分析技術(shù)。在分析過程中,編譯器需要對對象的引用路徑進(jìn)行全程追蹤,判斷其是否會被其他線程訪問、是否會通過方法返回或賦值跨出當(dāng)前作用域。特別是在存在復(fù)雜控制流、間接調(diào)用或反射的情況下,分析準(zhǔn)確性與代價都將急劇上升。
這種計算成本并非微不足道:在代碼編譯時長與運(yùn)行時性能收益之間,并不總是呈現(xiàn)出正向關(guān)系。在某些邊緣場景中,逃逸分析所帶來的優(yōu)化甚至可能因分析開銷過大、代碼形態(tài)不佳(例如過度拆箱、短生命周期對象)而無法收回性能投入。因此,Java虛擬機(jī)會采用熱點(diǎn)代碼觸發(fā)機(jī)制,僅對高頻路徑進(jìn)行逃逸分析,以期在收益與成本之間實(shí)現(xiàn)動態(tài)平衡。
雖然這項技術(shù)并不十分成熟,但是它也是即時編譯器優(yōu)化技術(shù)中一個十分重要的手段。
未完待續(xù)
很高興與你相遇!如果你喜歡本文內(nèi)容,記得關(guān)注哦!
本文來自博客園,作者:poemyang,轉(zhuǎn)載請注明原文鏈接:http://www.rzrgm.cn/poemyang/p/19027777
浙公網(wǎng)安備 33010602011771號