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