逃逸分析(Escape Analysis)是目前Java虛擬機中比較前沿的優化技術。
逃逸分析的基本行為就是分析對象動態作用域:當一個對象在方法中被定義后,它可能被外部方法所引用,例如作為調用參數傳遞到其他地方中,稱為方法逃逸。
例如:

StringBuffer sb是一個方法內部變量,上述代碼中直接將sb返回,這樣這個StringBuffer有可能被其他方法所改變,這樣它的作用域就不只是在方法內部,雖然它是一個局部變量,稱其逃逸到了方法外部。
甚至還有可能被外部線程訪問到,譬如賦值給類變量或可以在其他線程中訪問的實例變量,稱為線程逃逸。
上述代碼如果想要StringBuffer sb不逃出方法,可以這樣寫:

不直接返回StringBuffer,那么StringBuffer將不會逃逸出方法。
如果能證明一個對象不會逃逸到方法或線程外,則可能為這個變量進行一些高效的優化。
1. 棧上分配
我們都知道Java中的對象都是在堆上分配的,而垃圾回收機制會回收堆中不再使用的對象,但是篩選可回收對象,回收對象還有整理內存都需要消耗時間。如果能夠通過逃逸分析確定某些對象不會逃出方法之外,那就可以讓這個對象在棧上分配內存,這樣該對象所占用的內存空間就可以隨棧幀出棧而銷毀,就減輕了垃圾回收的壓力。
在一般應用中,如果不會逃逸的局部對象所占的比例很大,如果能使用棧上分配,那大量的對象就會隨着方法的結束而自動銷毀了。
2. 同步消除
線程同步本身比較耗時,如果確定一個變量不會逃逸出線程,無法被其它線程訪問到,那這個變量的讀寫就不會存在競爭,對這個變量的同步措施可以清除。
3. 標量替換
Java虛擬機中的原始數據類型(int,long等數值類型以及reference類型等)都不能再進一步分解,它們就可以稱為標量。相對的,如果一個數據可以繼續分解,那它稱為聚合量,Java中最典型的聚合量是對象。如果逃逸分析證明一個對象不會被外部訪問,並且這個對象是可分解的,那程序真正執行的時候將可能不創建這個對象,而改為直接創建它的若干個被這個方法使用到的成員變量來代替。拆散后的變量便可以被單獨分析與優化,可以各自分別在棧幀或寄存器上分配空間,原本的對象就無需整體分配空間了。
4. 總結
雖然概念上的JVM總是在Java堆上為對象分配空間,但並不是說完全依照概念的描述去實現;只要最后實現處理的“可見效果”與概念中描述的一直就沒問題了。所以說,“you can cheat as long as you don't get caught”。Java對象在實際的JVM實現中可能在GC堆上分配空間,也可能在棧上分配空間,也可能完全就消失了。這種行為從Java源碼中看不出來,也無法顯式指定,只是聰明的JVM自動做的優化而已。
但是逃逸分析會有時間消耗,所以性能未必提升多少,並且由於逃逸分析比較耗時,目前的實現都是采用不那么准確但是時間壓力相對較小的算法來完成逃逸分析,這就可能導致效果不穩定,要慎用。
由於HotSpot虛擬機目前的實現方法導致棧上分配實現起來比較復雜,因為在HotSpot中暫時還沒有做這項優化。
Reference:Java的reference等於C的指針。所以,在Java的方法調用中,reference也要復制一份壓入堆棧。在方法中對reference的操作就是對這個reference副本的操作。
相關JVM參數
-XX:+DoEscapeAnalysis 開啟逃逸分析
-XX:+PrintEscapeAnalysis 開啟逃逸分析后,可通過此參數查看分析結果。
-XX:+EliminateAllocations 開啟標量替換
-XX:+EliminateLocks 開啟同步消除
-XX:+PrintEliminateAllocations 開啟標量替換后,查看標量替換情況。
摘自互聯網