青島紀(jì)委網(wǎng)站廉政建設(shè)準(zhǔn)考證他達拉非片正確服用方法
開發(fā)者都知道,基本上所有對象都是在堆上創(chuàng)建。但是,這里還是沒有把話說絕對哈,指的是基本上所有。昨天一位朋友在聊天中,就說了所有對象都在堆中創(chuàng)建,然后被朋友一陣的嘲笑。
開始我們的正文,我們今天來聊聊關(guān)于逃逸分析。
逃逸分析(Escape Analysis)是目前Java虛擬機中比較前沿的優(yōu)化技術(shù)。這是一種可以有效減少Java 程序中同步負(fù)載和內(nèi)存堆分配壓力的跨函數(shù)全局?jǐn)?shù)據(jù)流分析算法。通過逃逸分析,Java Hotspot編譯器能夠分析出一個新的對象的引用的使用范圍從而決定是否要將這個對象分配到堆上。
逃逸分析的基本原理是:分析對象動態(tài)作用域,當(dāng)一個對象在方法里面被定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他方法中,這種稱為方法逃逸;甚至還有可能被外部線程訪問到,譬如賦值給可以在其他線程中訪問的實例變量,這種稱為線程逃逸;從不逃逸、方法逃逸到線程逃逸,稱為對象由低到高的不同逃逸程度。
開啟逃逸分析,編譯器可以對代碼進行如下優(yōu)化:
- 同步消除:如果一個對象被逃逸分析發(fā)現(xiàn)只能被一個線程所訪問,那對于這個對象的操作可以不同步。
- 棧上分配:如果確定一個對象不會逃逸出線程之外,那讓這個對象在棧上分配內(nèi)存將會是一個很不錯的主意,對象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀。
- 標(biāo)量替換:如果一個對象被逃逸分析發(fā)現(xiàn)不會被外部方法訪問,并且這個對象可以拆散,那么程序真正執(zhí)行的時候?qū)⒖赡懿蝗?chuàng)建這個對象,而改為直接創(chuàng)建它的若干個比這個方法使用的成員變量來代替。將對象拆分后,可以讓對象的成員變量在棧上分配和讀寫。
JVM中通過如下參數(shù)可以指定是否開啟逃逸分析:
-XX:+DoEscapeAnalysis :表示開啟逃逸分析(JDK 1.7之后默認(rèn)開啟)。
-XX:-DoEscapeAnalysis :表示關(guān)閉逃逸分析。
同步消除
線程同步本身是一個相對耗時的過程,如果逃逸分析能夠確定一個變量不會逃逸出線程,無法被其他線程訪問,那么這個變量的讀寫肯定就不會有競爭,對這個變量實施的同步措施也就可以安全地消除掉。
如以下代碼:
public void method() {Object o = new Object();synchronized (o) {System.out.println(o);}
}
對對象o加鎖,但是對象o的生命周期與方法method()一樣,所以不會被其他線程訪問到,不會發(fā)生線程安全問題,那么在JIT編譯階段會被優(yōu)化為如下所示:
public void method() {Object o = new Object();System.out.println(o);
}
這也被稱為鎖消除。
棧上分配
在Java虛擬機中,Java堆上分配創(chuàng)建對象的內(nèi)存空間幾乎是Java程序員都知道的常識,Java堆中的對象對于各個線程都是共享和可見的,只要持有這個對象的引用,就可以訪問到堆中存儲的對象數(shù)據(jù)。虛擬機的垃圾收集子系統(tǒng)會回收堆中不再使用的對象,但回收動作無論是標(biāo)記篩選出可回收對象,還是回收和整理內(nèi)存,都需要耗費大量資源。但是,存在一種特殊情況,如果逃逸分析確認(rèn)對象不會逃逸出線程之外,那么就可能被優(yōu)化成棧上分配。這樣就無需在堆上分配內(nèi)存,也無須進行垃圾回收了。
如以下代碼:
public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 1000000; i++) {alloc();}Thread.sleep(100000);
}private static void alloc() {User user = new User();
}
代碼很簡單,就是循環(huán)創(chuàng)建100萬次,使用alloc()方法創(chuàng)建100萬個User對象。這里的alloc()方法中定義了User對象并沒有被其他方法引用,所以符合棧上分配的要求。
JVM參數(shù)如下:
-Xmx2G -Xms2G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
啟動程序,通過jmap工具查看實例數(shù):
jmap -histo pidnum #instances #bytes class name
----------------------------------------------
1: 3771 2198552 [B
2: 10617 1722664 [C
3: 104057 1664912 com.miracle.current.lock.StackAllocationTest$User
我們可以看到程序總共創(chuàng)建了104057個User對象,遠小于100萬。我們可以關(guān)閉逃逸分析再來看下:
-Xmx2G -Xms2G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
啟動程序,通過jmap工具查看實例數(shù):
jmap -histo 42928num #instances #bytes class name
----------------------------------------------1: 628 22299176 [I2: 1000000 16000000 com.miracle.current.lock.StackAllocationTest$User
可以看到,關(guān)閉逃逸分析后總共創(chuàng)建了100萬個User對象。對比來看,棧上分配對堆內(nèi)存消耗,GC都有著重要的作用。
標(biāo)量替換
若一個數(shù)據(jù)已經(jīng)無法再分解成更小的數(shù)據(jù)來表示了,Java虛擬機中的原始數(shù)據(jù)類型(int 、long 等數(shù)值類型及reference類型等)都不能再進一步分解了,那么這些數(shù)據(jù)就可以被稱為標(biāo)量。相對的,如果一個數(shù)據(jù)可以繼續(xù)分解,那它就被稱為聚合量(Aggregate),Java中的對象就是典型的聚合量。
假如逃逸分析能夠證明一個對象不會被方法外部訪問,并且這個對象可以被拆散,那么程序真正執(zhí)行的時候?qū)⒖赡懿蝗?chuàng)建這個對象,而改為直接創(chuàng)建它的若干個被這個方法使用的成員變量來代替。
有如下代碼:
public static void main(String[] args) {method();
}private static void method() {User user = new User(25);System.out.println(user.age);
}private static class User {private int age;public User(int age) {this.age = age;}
}
在method()方法中創(chuàng)建User對象,指定age為25,這里User不會被其他方法引用,也就是說它不會逃逸出方法,并且User是可以拆解為標(biāo)量的。所以alloc()代碼會優(yōu)化為如下:
private static void alloc() {int age = 25;System.out.println(age);
}
總結(jié)
盡管目前逃逸分析技術(shù)仍在發(fā)展之中,未完全成熟,但它是即時編譯器優(yōu)化技術(shù)的一個重要前進方向,在日后的Java虛擬機中,逃逸分析技術(shù)肯定會支撐起一系列更實用、有效的優(yōu)化技術(shù)。