深入分析JVM逃逸分析对性能的影响
逃逸分析(Escape Analysis)
逃逸分析的基本行為就是分析對象動態作用域:當一個對象在方法中被定義后,它可能被外部方法所引用,稱為方法逃逸。甚至還有可能被外部線程訪問到,譬如賦值給類變量或可以在其他線程中訪問的實例變量,稱為線程逃逸。
方法逃逸的幾種方式如下:
public class EscapeTest {public static Object obj;public void globalVariableEscape() { // 給全局變量賦值,發生逃逸obj = new Object();}public Object methodEscape() { // 方法返回值,發生逃逸return new Object();}public void instanceEscape() { // 實例引用發生逃逸test(this); } }棧上分配
棧上分配就是把方法中的變量和對象分配到棧上,方法執行完后自動銷毀,而不需要垃圾回收的介入,從而提高系統性能。
同步消除
線程同步本身比較耗,如果確定一個對象不會逃逸出線程,無法被其它線程訪問到,那該對象的讀寫就不會存在競爭,對這個變量的同步措施就可以消除掉。單線程中是沒有鎖競爭。(鎖和鎖塊內的對象不會逃逸出線程就可以把這個同步塊取消)
標量替換
Java虛擬機中的原始數據類型(int,long等數值類型以及reference類型等)都不能再進一步分解,它們就可以稱為標量。相對的,如果一個數據可以繼續分解,那它稱為聚合量,Java中最典型的聚合量是對象。如果逃逸分析證明一個對象不會被外部訪問,并且這個對象是可分解的,那程序真正執行的時候將可能不創建這個對象,而改為直接創建它的若干個被這個方法使用到的成員變量來代替。拆散后的變量便可以被單獨分析與優化,
可以各自分別在棧幀或寄存器上分配空間,原本的對象就無需整體分配空間了。
棧上分配深入分析
public class OnStackTest {public static void alloc(){byte[] b=new byte[2];b[0]=1;}public static void main(String[] args) {long b=System.currentTimeMillis();for(int i=0;i<100000000;i++){alloc();}long e=System.currentTimeMillis();System.out.println(e-b);} }-XX:+DoEscapeAnalysis開啟逃逸分析(jdk1.8默認開啟,其它版本未測試)
-XX:-DoEscapeAnalysis 關閉逃逸分析
開啟逃逸分析,執行的時間為4毫秒。如下圖:
關閉逃逸分析,執行的時間為618毫秒,并且伴隨的大量的GC日志信息。如下圖:
**通過上面開啟和關閉逃逸分析:
開啟逃逸分析,對象沒有分配在堆上,沒有進行GC,而是把對象分配在棧上。
關閉逃逸分析,對象全部分配在堆上,當堆中對象存滿后,進行多次GC,導致執行時間大大延長。堆上分配比棧上分配慢上百倍。**
即時編譯器(Just-in-time Compilation,JIT)
1、使用client編譯器時,默認執行為1500次才認為是熱代碼;
2、使用server編譯器時,默認執行為10000次才認為是熱代碼;
上面的例子開啟逃逸分析后,并不是所有的對象都直接在棧上分配,而是通過JIT分析此代碼是熱代碼,才進行異步編譯成本地機器碼,并通過逃逸分析,把對象分配到棧上。(如果是server編譯器:在前10000次循環和編譯成本地機器碼這段時間,對象都會在堆中分配對象,編譯成本地機器碼后才會在棧上分配)
-XX:+EliminateAllocations開啟標量替換(jdk1.8默認開啟,其它版本未測試)
-XX:-EliminateAllocations 關閉標量替換
標量替換基于分析逃逸基礎之上,開啟標量替換必須開啟逃逸分析
關閉標量替換
這次我們打開逃逸分析,并且把標量替換功能關閉,我們發現對象又分配到堆里面了,并執行了多次GC。由此可以看出java中沒有實現真正意義上的棧上分配,而是通過標量替換來實現棧上分配的。
鎖消除深入分析
把上面的OnStackTest代碼稍微修改了下,加了一個同步塊。默認數組長度大于64的是不會在棧上分配的,我們都以堆上分配為例來測試鎖消除帶來的影響。
public class OnStackTest {public static void alloc(){byte[] b=new byte[65];synchronized (b) { //同步代碼塊b[0]=1;}}public static void main(String[] args) throws IOException {long b=System.currentTimeMillis();for(int i=0;i<100000000;i++){alloc();}long e=System.currentTimeMillis();System.out.println(e-b);} }-XX:+EliminateLocks開啟鎖消除(jdk1.8默認開啟,其它版本未測試)
-XX:-EliminateLocks 關閉鎖消除
鎖消除基于分析逃逸基礎之上,開啟鎖消除必須開啟逃逸分析
開啟鎖消除
關閉鎖消除
開啟鎖消除執行的時間為1807毫秒
關閉鎖消除執行的時間為3801毫秒
通過開啟和關閉鎖消除我們可以看到性能最少提升1倍以上。
本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8????
點擊這里快速進入簡書
總結
以上是生活随笔為你收集整理的深入分析JVM逃逸分析对性能的影响的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Trie Tree 实现中文分词器
- 下一篇: 多线程并发下的单例模式