1、先來個開胃菜(靈魂拷問):
下面這兩份代碼哪個好,(從jvm層面考慮的話)好在哪?
public StringBuffer createString1(String ... values){
StringBuffer stringBuffer = new StringBuffer();
for (String string : values) {
stringBuffer.append(string+" ");
}
return stringBuffer;
}
public String createString2(String ... values){
StringBuffer stringBuffer = new StringBuffer();
for (String string : values) {
stringBuffer.append(string+" ");
}
return stringBuffer.toString();
}
看完我下面的文章之後就知道了。
2、逃逸分析
逃逸分析是編譯語言中的一種優化分析,而不是一種優化的手段。通過對象的作用範圍的分析,爲其他優化手段提供分析數據從而進行優化。
是不是聽得有點懵?用“土鱉”一點的大白話來說就是,在方法內你new出來的對象只能在方法內隨便折騰(業務邏輯處理),但是你不能逃逸出方法的這個一畝三分地而爲外部方法所利用。再精簡一點可以理解成:對象在我方法內活着,出方法則消亡。
逃逸分析的場景包括:
- 全局變量賦值逃逸
- 方法返回值逃逸
- 實例引用發生逃逸
- 線程逃逸:賦值給類變量或可以在其他線程中訪問的實例變量
public class EscapeAnalysis {
public static Object object;
//全局變量賦值逃逸
public void globalVariableEscape(){
object =new Object();
}
//方法返回值逃逸:方法內new出來的對象逃離案發現場(即方法範圍)而有機會被其他方法獲取該對象
public Object methodEscape(){
return new Object();
}
//實例引用發生逃逸
public void instancePassEscape(){
this.speak(this);
}
public void speak(EscapeAnalysis escapeAnalysis){
System.out.println("Escape Hello");
}
}
-XX:+DoEscapeAnalysis 開啓逃逸分析
-XX:-DoEscapeAnalysis 關閉逃逸分析
通過jmap -histo [pid]查看java堆上的對象分佈情況:
2、標量替換
2.1、標量和聚合量
標量(scalar replacement):就是不能再被分解的量。(例如八大基本類型 byte , short , int ,long ,char , float ,double , boolean)另外指向對象的引用也是標量。
聚合量(aggregate)就是還能繼續被分解的量,例如對象能被分解成多個標量。
如果把一個Java對象拆散,將其成員變量恢復爲分散的變量,這就叫做標量替換。拆散後的變量便可以被單獨分析與優化,可以各自分別在活動記錄(棧幀或寄存器)上分配空間;原本的對象就無需整體分配空間了。
2.2、替換過程
通過逃逸分析確定該對象不會被外部訪問,並且對象可以被進一步分解時,JVM不會創建該對象,而會將該對象成員變量分解若干個被這個方法使用的成員變量所代替。這些代替的成員變量在棧幀或寄存器上分配空間。
-XX:+EliminateAllocations 可以開啓標量替換
-XX:+PrintEliminateAllocations 查看標量替換情況(Server VM 非Product版本支持)
3、棧上分配
震驚?!不是所有的對象都在堆上分配內存空間!
只有發生了逃逸的對象,纔會在堆上分配內存,JIT即使編譯技術優化中加入了逃逸分析,就是專門分析對象是否發生逃逸的,如果發現對象的作用域僅限於本方法內,也就是外部沒有任何引用,那麼就讓對象在棧幀執行到該方法時在棧上分配內存。隨着方法執行完畢,返回一個標量基本數據類型後,棧幀彈出棧,所有分配的內存回收,當然也包括那個對象的內存,所以這效率肯定比GC不知道啥時候纔來打掃衛生要高得多。
4、同步消除(又稱爲鎖消除)
通過-XX:+EliminateLocks可以開啓同步消除,進行測試執行的效率
同步消除是java虛擬機提供的一種優化技術。這裏消除的其實是對象的同步鎖,堆被線程共享,那麼堆上面的對象也會被線程共享,大家一起讀寫,會出現同步的併發問題,所以對象也加了synchronized同步鎖。但是棧是線程獨享的,如果進行棧上分配,那麼就根本不需要同步鎖,提高了執行效率。這就是同步消除。
5、做逃逸分析有什麼好處
經過上面的總結可以發現:
(1)消除了同步鎖,提高了運行速度。
(2)減輕了垃圾收集子系統GC的負擔,標量替換使得減少了對象回收的一串漫長流程。
(3)提高了內存利用率,棧上分配可以更好管理內存使用和回收。
震驚?!這麼多好處!JDK中逃逸分析居然還不成熟?!
這個概念書上說1999年就提出來了,是非常好的一個想法,但是直到現在也還不成熟,因爲代碼編寫的原因,沒有嚴格限制返回類型,圖方便我也會整個對象返回。又因爲整個逃逸分析流程較長(整套逃逸分析,標量替換,棧上分配,同步消除,整套大寶劍做下來也很耗費性能資源)。結果就造成整套分析下來全部對象還要乖乖在堆上分配內存。速度更慢了。
但是這是即時編譯技術JIT的非常好的一個模式,相信將來會有更大的發展空間。
最後良心贈送一張JVM底層對對象的處理流程圖,大家自己去理解吧,本文就不再展開了講解了。
本文最後給大家一點個人的小建議,其實所有的知識都是串聯的。希望大家通過本文能夠將JVM逃逸分析,synchronized鎖的底層實現,JVM垃圾回收等知識融會貫通形成自己的知識體系,同時反思自己平常寫的代碼中是否有注意到進行標量替換來提升代碼執行效率。