棧上分配是JVM的一個優化選項。java
Java的對象通常都是分配在堆內存中的,而JVM開啓了棧上分配後,容許把線程私有的對象(其它線程訪問不到的對象)打散分配在棧上。這些分配在棧上的對象在方法調用結束後即自行銷燬,不須要JVM觸發垃圾回收器來回收,所以提高了JVM的性能。bash
棧上分配在JDK6u23後默認是開啓了的。下面經過代碼來驗證這一點。jvm
寫一段代碼:性能
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
:指定最大堆內存spa
-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環境下執行的結果:code
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的長。server
而JDK6u23的執行過程當中沒有打印任何GC日誌,證實這時候alloc()
方法中new出來的User對象,是存放在alloc()
方法對應的棧幀上的。當每一次執行alloc()
方法,線程會向Java棧壓入一個棧幀,new User()
建立的對象就存放在這個棧幀上;alloc()
方法執行結束,棧幀從Java棧彈出,user對象隨棧幀的彈出銷燬。
示意圖:
在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參數中關閉了兩者其中一個選項,棧上分配都不會生效。