棧上分配是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引數
在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引數中關閉了二者其中一個選項,棧上分配都不會生效。