据我所知,JVM 使用逃逸分析来进行一些性能优化,例如锁粗化和锁省略。如果 JVM 有可能决定任何特定对象可以使用转义分析在堆栈上分配,我很感兴趣。
一些资源让我认为我是对的。有没有真正做到这一点的JVM?
使用这个版本的 java -XX:+DoEscapeAnalysis 可以大大减少 gc 活动和 14 倍的执行速度。
$ java -version
java version "1.6.0_14"
Java(TM) SE Runtime Environment (build 1.6.0_14-b08)
Java HotSpot(TM) Client VM (build 14.0-b16, mixed mode, sharing)
$ uname -a
Linux xxx 2.6.18-4-686 #1 SMP Mon Mar 26 17:17:36 UTC 2007 i686 GNU/Linux
没有逃逸分析,
$ java -server -verbose:gc EscapeAnalysis|cat -n
1 start
2 [GC 896K->102K(5056K), 0.0053480 secs]
3 [GC 998K->102K(5056K), 0.0012930 secs]
4 [GC 998K->102K(5056K), 0.0006930 secs]
--snip--
174 [GC 998K->102K(5056K), 0.0001960 secs]
175 [GC 998K->102K(5056K), 0.0002150 secs]
176 10000000
通过逃逸分析,
$ java -server -verbose:gc -XX:+DoEscapeAnalysis EscapeAnalysis
start
[GC 896K->102K(5056K), 0.0055600 secs]
10000000
执行时间通过逃逸分析显着减少。为此,循环更改为 10e9 次迭代,
public static void main(String [] args){
System.out.println("start");
for(int i = 0; i < 1000*1000*1000; ++i){
Foo foo = new Foo();
}
System.out.println(Foo.counter);
}
没有逃逸分析,
$ time java -server EscapeAnalysis
start
1000000000
real 0m27.386s
user 0m24.950s
sys 0m1.076s
通过逃逸分析,
$ time java -server -XX:+DoEscapeAnalysis EscapeAnalysis
start
1000000000
real 0m2.018s
user 0m2.004s
sys 0m0.012s
因此,使用逃逸分析的示例运行速度比非逃逸分析运行快 14 倍。
我认为它不会逃避堆栈分配的分析。例子:
public class EscapeAnalysis {
private static class Foo {
private int x;
private static int counter;
public Foo() {
x = (++counter);
}
}
public static void main(String[] args) {
System.out.println("start");
for (int i = 0; i < 10000000; ++i) {
Foo foo = new Foo();
}
System.out.println(Foo.counter);
}
}
与-server -verbose:gc -XX+DoEscapeAnalysis
:
开始 [GC 3072K->285K(32640K), 0.0065187 秒] [GC 3357K->285K(35712K), 0.0053043 秒] [GC 6429K->301K(35712K),0.0030797 秒] [GC 6445K->285K(41856K), 0.0033648 秒] [GC 12573K->285K(41856K), 0.0050432 秒] [GC 12573K->301K(53952K),0.0043682 秒] [GC 24877K->277K(53952K), 0.0031890 秒] [GC 24853K->277K(78528K), 0.0005293 秒] [GC 49365K->277K(78592K), 0.0006699 秒] 10000000
据称JDK 7 支持堆栈分配。
越狱分析真的很不错,但它不是一个完整的无监狱卡。如果您在对象内部有一个动态大小的集合,则转义分析将不会从堆切换到堆栈。例如:
public class toEscape {
public long l;
public List<Long> longList = new ArrayList<Long>();
}
即使这个对象是在一个方法中创建的并且绝对不会从句法的角度转义,编译器也不会将其标记为转义。我怀疑是因为从纯句法的角度来看,longList 的大小并没有真正的限制,它可能会破坏你的堆栈。因此,我相信它需要通过这个案例。我在 longList 为空的情况下对此进行了实验,但它仍然在一个简单的微基准测试中引起了集合。</p>