Battle:你會TLAB,我會逃逸分析

「噔噔噔…」傳來一陣敲門聲,把我從好夢中驚醒了。java

朦朧間聽到有人在說話「阿Q,在家不?」面試

「來了來了」,推門一看,原來是「趙信」兄弟。併發

趙信:自稱常山趙子龍,一把三爪長槍耍的虎虎生風,見人上去就是一槍,人送外號「菊花信」。app


 

TLAB













  • 儘管不是全部的對象實例都可以在TLAB中成功分配內存(由於它的空間比較小),但JVM明確是將TLAB做爲內存分配的首選;
  • 一旦對象在TLAB空間分配內存失敗時,JVM就會嘗試着經過使用加鎖機制確保數據操做的原子性,從而直接在Eden空間中分配內存。
     

參數設置ide

  • -XX:UseTLAB:設置是否開啓TLAB空間;
  • -XX:TLABWasteTargetPercent:設置TLAB空間所佔Eden空間的百分比大小,默認僅佔1%;


堆是分配對象的惟一選擇嗎?





  1. 若是通過逃逸分析(Escape Analysis)後發現,一個對象並無逃逸出方法,那麼就可能被優化爲棧上分配。這樣就無需在堆上分配內存,也無須進行垃圾回收了。這也是最多見的堆外存儲技術。
  2. 基於OpenJDK深度定製的TaoBaoVM,它創新的GCIH(GCinvisible heap)實現了堆外分配。將生命週期較長的Java對象從堆中移至堆外,而且GC不能管理GCIH內部的Java對象,以此達到下降GC的回收頻率和提高GC的回收效率的目的。




舉例一性能

public void method(){
   
    User user = new User();
    ...
    user = null;
}

user對象在方法內部聲明,且在內部置爲null,未被方法外的方法所引用,咱們就說user對象沒有發生逃逸。學習

能夠分配到棧上,並隨着方法的結束,棧空間也隨之移除。測試

舉例二優化

public static StringBuffer createStringBuffer(String s1,String s2){
   
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}

雖然sb對象在方法內部被定義,可是它又做爲方法的返回對象,可被其它方法調用,咱們就說sb對象發生了逃逸。this

要想不發生逃逸,能夠改造爲:

public static String createStringBuffer(String s1,String s2){
   
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}



JDK 6u23版本以後,HotSpot中默認開啓了逃逸分析。

  • -XX:DoEscapeAnalysis:顯式開啓逃逸分析
  • -XX:+PrintEscapeAnalysis:查看逃逸分析的篩選結果



棧上分配


/** * 棧上分配測試 * -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails */
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 {
   

    }
}

逃逸分析默認開啓,也能夠手動開啓:-XX:+DoEscapeAnalysis

關閉逃逸分析


同步省略


咱們都知道線程同步的代價是至關高的,同步的後果就是下降了併發性和性能。

JVM爲了提升性能,在動態編譯同步塊的時候,JIT編譯器能夠藉助逃逸分析來判斷同步塊所使用的鎖對象是否只可以被一個線程訪問。

若是符合條件,那麼JIT編譯器在編譯這個同步塊的時候就會取消對這部分代碼的同步。這個取消同步的過程就叫同步省略,也叫鎖消除。

舉例

public class SynchronizedTest {
   
    public void method() {
   
        Object code = new Object();
        synchronized(code) {
   
            System.out.println(code);
        }
    }
    /** *代碼中對code這個對象進行加鎖, *可是code對象的生命週期只在method方法中 *並不會被其餘線程所訪問控制, *因此在 JIT 編譯階段就會被優化掉。 */
    
    //優化爲
    public void method2() {
   
        Object code = new Object();
        System.out.println(code);
    }
}

在解釋執行時這裏仍然會有鎖,可是通過服務端編譯器的即時編譯以後,這段代碼就會忽略全部的同步措施而直接執行。

標量替換

  • 標量:不可被進一步分解的量,如JAVA的基本數據類型就是標量;
  • 聚合量:能夠被進一步分解的量, 在JAVA中對象就是能夠被進一步分解的聚合量。

聚合量能夠分解成其它標量和聚合量。

標量替換,又名分離對象,即在JIT階段,若是通過逃逸分析,發現一個對象不會被外界訪問的話,那麼通過JIT優化,就會把這個對象拆解成若干個其中包含的成員變量來替代。

舉例

public class ScalarTest {
   
    public static void main(String[] args) {
   
        alloc();   
    }
    public static void alloc(){
   
        Point point = new Point(1,2);
    }
}
class Point{
   
    private int x;
    private int y;
    public Point(int x,int y){
   
        this.x = x;
        this.y = y;
    }
}
//轉化以後變爲
public static void alloc(){
   
    int x = 1;
    int y = 2;
}
//Point這個聚合量通過逃逸分析後,發現他並無逃逸,就被替換成兩個標量了。

標量替換默認開啓,你也能夠經過參數手動設置-XX:+EliminateAllocations,開啓以後容許將對象打散分配到棧上,GC減小,執行速度提高。

常見的發生逃逸的場景


舉例

public class EscapeAnalysis {
   

    public EscapeAnalysis obj;
    
     /* 爲成員屬性賦值,發生逃逸 */
    public void setObj(){
   
        this.obj = new EscapeAnalysis();
    }
    //思考:若是當前的obj引用聲明爲static的?仍然會發生逃逸。

    /* 方法返回EscapeAnalysis對象,發生逃逸 */
    public EscapeAnalysis getInstance(){
   
        return obj == null? new EscapeAnalysis() : obj;
    }
   
   
    /* 引用成員變量的值,發生逃逸 */
    public void useEscapeAnalysis1(){
   
        EscapeAnalysis e = getInstance();
        //getInstance().xxx()一樣會發生逃逸
    }
    
     /* 對象的做用域僅在當前方法中有效,沒有發生逃逸 */
    public void useEscapeAnalysis(){
   
        EscapeAnalysis e = new EscapeAnalysis();
    }
}

逃逸分析並不成熟




1999年就已經發表了關於逃逸分析的論文,但JDK1.6中才有實現,並且這項技術到現在也不是十分紅熟。

其根本緣由就是沒法保證逃逸分析的性能提高必定能高於它的消耗,由於逃逸分析自身也須要進行一系列複雜的分析,是須要耗時的。

一個極端的例子,就是通過逃逸分析以後,發現全部對象都逃逸了,那這個逃逸分析的過程就白白浪費掉了。

細心的小夥伴也應該能發現,咱們在抽樣器中的截圖其實就是在堆中分配的對象。

以上就是今天的全部內容了,若是你有不一樣的意見或者更好的idea,歡迎聯繫阿Q,添加阿Q能夠加入技術交流羣參與討論呦!

後臺留言領取java乾貨資料:學習筆記與大廠面試題

相關文章
相關標籤/搜索