JVM的棧上分配

煲煲菜發表於2018-11-09

棧上分配是JVM的一個優化選項。

Java的物件一般都是分配在堆記憶體中的,而JVM開啟了棧上分配後,允許把執行緒私有的物件(其它執行緒訪問不到的物件)打散分配在棧上。這些分配在棧上的物件在方法呼叫結束後即自行銷燬,不需要JVM觸發垃圾回收器來回收,因此提升了JVM的效能。

棧上分配在JDK6u23後預設是開啟了的。下面通過程式碼來驗證這一點。

驗證

寫一段程式碼:

public class OnStackTest {
    // User類
    public static class User{
        public int id=0;
        public String name="";
    }
    // 建立User類物件
    public static void alloc(){
        User u=new User();
        u.id=5;
        u.name="geym";
    }
    // 程式入口
    public static void main(String[] args) throws InterruptedException {
        long b=System.currentTimeMillis();
        // 建立大量的物件
        for(int i=0;i<99999999;i++){
            alloc();
        }
        long e=System.currentTimeMillis();
        // 列印執行時間
        System.out.println(e-b);
    }
}
複製程式碼

JDK6u22環境下執行的結果:

D:\develop\jdk\6u22\bin\java.exe -Xmx5m -Xms5m -XX:+PrintGC geym.zbase.ch2.onstackalloc.OnStackTest
[GC 2048K->288K(5824K), 0.0049399 secs]
[GC 2336K->288K(5824K), 0.0013872 secs]
[GC 2336K->320K(5824K), 0.0026034 secs]
......
[GC 3280K->720K(6080K), 0.0001026 secs]
3304
複製程式碼

上面引數的含義:

-Xmx:指定最大堆記憶體
-Xms:指定最大堆記憶體
-XX:+PrintGC:列印GC日誌,+號表示啟用,-號表示禁用

JDK6u23環境下執行的結果:

D:\develop\jdk\6u23\bin\java.exe -Xmx5m -Xms5m -XX:+PrintGC geym.zbase.ch2.onstackalloc.OnStackTest
70
複製程式碼

從以上兩個執行的結果可以看出,JDK6u22預設是沒有開啟棧上分配的,所以alloc()方法中new出來的User物件,是存放在堆上的。由於指定了最大堆記憶體只有5m,當堆記憶體不足就會觸發GC。從JDK6u22的GC日誌可以看出執行過程頻繁觸發GC,執行耗時3304,明顯比JDK6u23的長。

而JDK6u23的執行過程中沒有列印任何GC日誌,證明這時候alloc()方法中new出來的User物件,是存放在alloc()方法對應的棧幀上的。當每一次執行alloc()方法,執行緒會向Java棧壓入一個棧幀,new User()建立的物件就存放在這個棧幀上;alloc()方法執行結束,棧幀從Java棧彈出,user物件隨棧幀的彈出銷燬。

示意圖:

JVM的棧上分配

相關JVM引數

JDK6u22及之前的版本如果需要使用棧上分配來優化,可以加入以下引數:

java -server -Xmx5m -Xms5m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:-UseTLAB -XX:+EliminateAllocations  geym.zbase.ch2.onstackalloc.OnStackTest
複製程式碼

-XX:+DoEscapeAnalysis:開啟逃逸分析

-XX:-UseTLAB:禁用TLAB(Thread Local Allocation Buffer)

-XX:+EliminateAllocations:開啟標量替換

關於逃逸分析

逃逸分析的作用就是判斷一個物件的作用域有沒有可能逃出一個Java方法的作用域。請看下面例子演示:

// u物件逃出alloc的作用域,不符合棧上分配的條件
public class OnStackTest {
    private static User u;
    public static void alloc(){
        u=new User();
    }
}
複製程式碼
// u物件沒有逃出alloc的作用域,符合棧上分配的條件
public class OnStackTest {
    public static void alloc(){
        User u=new User();
    }
}
複製程式碼

關於標量替換

啟用標量替換後,允許把物件打散分配在棧上。 比如user物件有id和name屬性,在啟用標量替換後,user物件的id和name屬性會視為區域性變數分配在棧上

注意:逃逸分析和標量替換是棧上分配的前提,所以,在jvm引數中關閉了二者其中一個選項,棧上分配都不會生效。

相關文章