JVM的棧上分配
棧上分配是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 環境下執行的結果:
-Xmx
:指定最大堆記憶體
-Xms
:指定最大堆記憶體
-XX:+PrintGC
:列印GC日誌, +
號表示啟用, -
號表示禁用
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 複製程式碼
在 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:+EliminateAllocationsgeym.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引數中關閉了二者其中一個選項,棧上分配都不會生效。