JVM內存增強之逃逸分析

概念

逃逸分析一種數據分析算法,基於此算法可以有效減少 Java 對象在堆內存中的分配。 Hotspot 虛擬機的編譯器能夠分析出一個新對象的引用范圍,然後決定是否要將這個對象分配到堆上.

 當一個對象在方法中被定義後,對象隻在方法內部使用,則認為沒有發生逃逸。

 當一個對象在方法中被定義後,它被外部方法所引用,則認為發生逃逸。

//對象發生瞭逃逸,不會在棧上分配,有可能導致GC STW
public StringBuffer append(String s1, String s2) {
 StringBuffer sb = new StringBuffer();
 sb.append(s1);
sb.append(s2);
 return sb;
}
//對象未發生逃逸
public String append(String s1, String s2) {
 StringBuffer sb = new StringBuffer();
 sb.append(s1);
 sb.append(s2);
 return sb.toString();
}

建議:開發中能在方法內部應用對象的,就盡量控制在內部

逃逸分析參數設計

在 JDK 1.7 版本之後, HotSpot 中默認就已經開啟瞭逃逸分析,如果使用的是較早的

版本,開發人員則可以通過:

✓ 選項“ -XX:+DoEscapeAnalysis" 顯式開啟逃逸分析。

✓ 通過選項“ -XX:+PrintEscapeAnalysis" 查看逃逸分析的篩選結果。

使用逃逸分析

編譯器可以對代碼做如下優化

1.棧上分配:將堆分配轉化為棧分配。如果一個對象在方法內創建,要使指向該對象的引用不會發生逃逸,對象可能是棧上分配的候選

/**
* 棧上分配測試(-XX:+DoEscapeAnalysis)
* -Xmx128m -Xms128m -XX:+DoEscapeAnalysis -XX:+PrintGC
*/
public class ObjectStackAllocationTests {
 public static void main(String[] args) throws InterruptedException {
 long start = System.currentTimeMillis();
 for (int i = 0; i < 10000000; i++) {
 alloc();
 }
 long end = System.currentTimeMillis();
 System.out.println("花費的時間為: " + (end - start) + " ms");
 // 為瞭方便查看堆內存中對象個數,線程 sleep
 TimeUnit.MINUTES.sleep(5);
 }
 private static void alloc() {
 byte[] data = new byte[10];//未發生逃逸
 }
}

2.同步鎖消除:

我們知道線程同步是靠犧牲性能來保證數據的正確性,這個過程的代價會非常高。程序 的並發行和性能都會降低。JVM 的 JIT 編譯器可以借助逃逸分析來判斷同步塊所使用的鎖對象是否隻能夠被一個線程應用?假如是,那麼 JIT 編譯器在編譯這個同步塊的時候就會取消對這部分代碼上加的鎖。這個取消同步的過程就叫同步省略,也叫鎖消除

public class SynchronizedLockTest {
 public void lock() {
 Object obj= new Object();
 synchronized(obj) {
 System.out.println(obj);
 }
}

3.標量替換分析

所謂的標量(scalar)一般指的是一個無法再分解成更小數據的數據。例如,Java 中 的原始數據類型就是標量。相對的,那些還可以分解的數據叫做聚合量(Aggregate),Java 中的對象就是聚合量,因為他可以分解成其他聚合量和標量。在 JIT 階段,如果經過逃逸分析,發現一個對象不會被外界訪問的話,那麼經過 JIT 優化,就會把這個對象分解成若幹個變量來代替。這個過程就是標量替換。

public class ObjectScalarReplaceTests {
 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");
 }
 private static void alloc() {
 Point point = new Point(1,2);
 }
 static class Point {
 private int x;
 private int y;
 public Point(int x,int y){
 this.x=x;
 this.y=y;
 }
 }
//對於上面代碼,假如開啟瞭標量替換,那麼 alloc 方法的內容就會變為如下形式
private static void alloc() {
     int x=10;
     int y=20;
 }

alloc 方法內部的 Point 對象是一個聚合量,這個聚合量經過逃逸分析後,發現他並沒有逃逸,就被替換成兩個標量瞭。那麼標量替換有什麼好處呢?可以大大減少堆內存的占用。因為一旦不需要創建對象瞭,那麼就不再需要分配堆內存瞭。標量替換為棧上分配 提供瞭很好的基礎。 

FAQ

1.什麼是逃逸分析?

可以有效減少 Java 對象在堆內存中的分配壓力和同步負載的算法

2.逃逸分析有什麼優勢、劣勢?

逃逸分析是需要消耗一定的性能去執行分析的,所以說如果方法中的對象全都是處於逃逸狀態,那麼就沒有起到優化的作用,從而就白白損失瞭這部分的性能消耗

到此這篇關於JVM內存增強之逃逸分析的文章就介紹到這瞭,更多相關JVM逃逸內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: