JVM-徹底搞懂 逃逸分析&標量替換

在這裏插入圖片描述

Pre

JVM-剖析對象內存分配流程


對象分配流程總覽

在這裏插入圖片描述


逃逸分析所處的階段

通過上圖的對象分配流程,我們可以知道逃逸分析是發生在第一步判斷對象是否可以在棧上分配的時候, 在棧上分配的目的是爲了減少將對象分配到堆上的概率,節約堆內存,減少GC壓力。

逃逸分析是JVM爲了優化對象分配而做的一種優化措施。


示例說明逃逸分析的含義

那逃逸分析的標準是什麼呢? 經過逃逸分析以後什麼樣的對象可以在棧上分配,什麼樣的對象不可以在棧上分配呢?

JVM通過逃逸分析確定該對象不會被外部訪問。如果不會逃逸可以將該對象在棧上分配內存,這樣該對象所佔用的內存空間就可以隨棧幀出棧而銷燬,減輕GC的壓力。

對象逃逸分析就是分析對象動態作用域 。 當一個對象在方法中被定義後,它可能被外部方法所引用。

舉個例子

public User doSomething1() {
   Artisan artisan1= new Artisan();
   artisan1.setId(1);
   artisan1.setDesc("artisan1");
   // ......
   return user;
}

public void doSomething2() {
   Artisan artisan2= new Artisan();
   artisan2.setId(2);
   artisan2.setDesc("artisan2");
   // ...... 
}

doSomething1返回對象,在方法結束之後被返回了,那這個對象的作用域範圍是不確定的。

doSomething2方法可以非常確定的是當方法結束以後,artisan2這個對象就失效了,因爲它的作用域範圍是當前方法。 那對於這樣的對象,JVM會把這樣的對象分配到線程棧中,讓它隨着方法結束時跟隨線程棧內存一起被回收掉。


逃逸分析的對象分配的方式【標量替換】

標量替換的含義

通過逃逸分析確定該對象不會被外部訪問,並且對象可以被進一步分解時,JVM不會創建該對象,而是將該對象成員變量分解若干個被這個方法使用的成員變量所代替,這些代替的成員變量在棧幀或寄存器上分配空間,這樣就不會因爲沒有一大塊連續空間導致對象內存不夠分配。

開啓標量替換參數(-XX:+EliminateAllocations),JDK7之後默認開啓


標量 VS 聚合量

標量替換 ? 那什麼是標量 ?

  • 標量: 不可被進一步分解的量,而JAVA的基本數據類型就是標量(比如int,long等基本數據類型以及reference類型等) 。

  • 聚合量: 標量的對立就是可以被進一步分解的量,稱之爲聚合量。 在JAVA中對象就是可以被進一步分解的聚合量。


JVM 參數 -XX:+DoEscapeAnalysis & -XX:+EliminateAllocations

JDK7之後默認開啓逃逸分析 .

如果需要關閉逃逸分析 -XX:-DoEscapeAnalysis 即可,不推薦修改該參數。

-XX:+EliminateAllocations 開啓標量替換參數 . 該參數的前提是開啓了逃逸分析,如果沒有開啓逃逸分析,僅開啓該參數無效。


棧上分配Demo

Code

public class Artisan {
    private int id ;
    private String desc ;
    // set get
    
}
/**
 * 棧上分配,標量替換
 *
 * 示例代碼調用了1億次alloc(),如果是分配到堆上,大概需要1GB以上堆空間,如果堆空間小於該值,必然會觸發GC。
 *
 * 使用如下參數不會發生GC
 * -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
 *
 * 使用如下參數都會發生大量GC
 * -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
 * -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
 */
public class AllocationOnThreadStackTest {

    public static void main(String[] args) throws InterruptedException {
        long begin = System.currentTimeMillis();
        allocate();
        System.out.println("cost:" + (System.currentTimeMillis() - begin));
    }


    private static void allocate() throws InterruptedException {
        for (int i = 0; i < 100000000; i++) {
            Artisan artisan = new Artisan();
            artisan.setId(1);
            artisan.setDesc("artisan");
        }
    }
}

【默認開啓逃逸分析】

-Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations

-XX:+DoEscapeAnalysis -XX:+EliminateAllocations JDK8 默認開啓,可以不設置。 這裏只是顯式設置,讓讀者更容易理解。

在這裏插入圖片描述

只有在程序開始之前GC了一次 (這是JVM內部複雜的機制決定的), 運行過程中,並沒有發生Minor GC .


【關閉逃逸分析】

看下關閉逃逸分析的效果

  • -DoEscapeAnalysis +EliminateAllocations
-Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations

在這裏插入圖片描述

在這裏插入圖片描述

發生了578次 Minor GC , 耗時 1369毫秒。 可見GC過多,對性能的影響是非常大的。


  • +DoEscapeAnalysis -EliminateAllocations

再看看 -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations

在這裏插入圖片描述

綜上所述,開啓了逃逸分析,對堆內存的節省,減少GC壓力,提高程序性能大有裨益。

最後說一句, 棧上空間不足,那一定會往堆上分配。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章