使用逃逸分析-進行代碼優化
使用逃逸分析,編譯器可以對代碼做如下優化:
一、棧上分配。將堆分配轉化為棧分配。如果一個對象在子程序中被分配,要使指向該對象的指針永遠不會逃逸,對象可能是棧分配的候選,而不是堆分配。
JIT編譯器在編譯期間根據逃逸分析的結果,發現如果一個對象並沒有逃逸出方法的話,就可能被優化成棧上分配。分配完成后,繼續在調用棧內執行,r最后線程結束,棧空間被回收,局部變量對象也被回收。這樣就無須進行垃圾回收了。
常見的棧上分配的場景:在逃逸分析中,已經說明了。分別是給成員變量賦值、方法返回值、實例引用傳遞。
public class EscapeAnalysis {
public EscapeAnalysis obj; /* 方法返回EscapeAnalysis對象,發生逃逸 */ public EscapeAnalysis getInstance(){ return obj == null? new EscapeAnalysis() : obj; } /* 為成員屬性賦值,發生逃逸 */ public void setObj(){ this.obj = new EscapeAnalysis(); } //思考:如果當前的obj引用聲明為static的?仍然會發生逃逸。 /* 對象的作用域僅在當前方法中有效,沒有發生逃逸 */ public void useEscapeAnalysis(){ EscapeAnalysis e = new EscapeAnalysis(); } /* 引用成員變量的值,發生逃逸 */ public void useEscapeAnalysis1(){ EscapeAnalysis e = getInstance(); //getInstance().xxx()同樣會發生逃逸 } }
二、同步省略(鎖消除)。如果一個對象被發現只能從一個線程被訪問到,那么對於這個對象的操作可以不考慮同步。
線程同步的代價是相當高的,同步的后果是降低並發性和性能。在動態編譯同步塊的時候,JIT編譯器可以借助逃逸分析來判斷同步塊所使用的鎖對象是否只能夠被一個線程訪問而沒有被發布到其他線程。如果沒有,那么JIT編譯器在編譯這個同步塊的時候就會取消對這部分代碼的同步。這樣就能大大提高並發性和性能。這個取消同步的過程就叫同步省略,也叫鎖消除。
public class SynchronizedTest { public void f() { Object hollis = new Object(); synchronized(hollis) { System.out.println(hollis); } } }
上述代碼在執行的時候如果開啟了逃逸分析,JIT編譯器在編譯這個同步塊的時候就會取消對這部分代碼的同步
三、分離對象或標量替換。有的對象可能不需要作為一個連續的內存結構存在也可以被訪問到,那么對象的部分(或全部)可以不存儲在內存,而是存儲在cPu寄存器中。
標量(Scalar)是指一個無法再分解成更小的數據的數據。Java中的原始數據類型就是標量。
相對的,那些還可以分解的數據叫做聚合量(Aggregate),Java中的對象就是聚合量,因為他可以分解成其他聚合量和標量。
在JIT階段,如果經過逃逸分析,發現一個對象不會被外界訪問的話,那么經過JITr優化,就會把這個對象拆解成若干個其中包含的若干個成員變量來代替。這個過程就是標量替換。
標量替換參數設置:參數-XX:+EliminateAllocations:開啟了標量替換(默認打開),允許將對象打散分配在棧上。
public class ScalarReplace { public static class User { public int id; public String name; } public static void alloc() { User u = new User();//未發生逃逸 標量替換等價於User u = new User()-> int id, String name; u.id = 5; u.name = "www.atguigu.com"; } public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { alloc(); } long end = System.currentTimeMillis(); System.out.println("花費的時間為: " + (end - start) + " ms"); } }
下面是逃逸分析-棧上分配的調試結果:
jvm開啟逃逸分析的參數: -XX:-DoEscapeAnalysis 不開啟,-XX:+DoEscapeAnalysis 開啟(默認開啟)
調試類:
/** * 棧上分配測試 * -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails * @author shkstart shkstart@126.com * @create 2020 10:31 */ public class StackAllocation { public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { alloc(); } // 查看執行時間 long end = System.currentTimeMillis(); System.out.println("花費的時間為: " + (end - start) + " ms"); // 為了方便查看堆內存中對象個數,線程sleep try { Thread.sleep(1000000); } catch (InterruptedException e1) { e1.printStackTrace(); } } private static void alloc() { User user = new User();//未發生逃逸 } static class User { } }
不開啟的情況:
開啟的情況:
當內存下調到256m時,不開啟逃逸分析,將會發生GC,而開啟了逃逸分析之后沒有發生GC並且比不開啟時執行速度要快;