使用Java 7进行转义分析/堆栈分配的资格

我正在使用Java 7中的转义分析进行一些测试,以便更好地了解哪些对象有资格进行堆栈分配。

这是我编写的用于测试堆栈分配的代码:

import java.util.ArrayList; import java.util.Iterator; public class EscapeAnalysis { private static final long TIME_TO_TEST = 10L * 1000L; // 10s static class Timestamp { private long millis; public Timestamp(long millis) { this.millis = millis; } public long getTime() { return millis; } public void setTime(long time) { millis = time; } } public static void main(String[] args) { long r = 0; System.out.println("test1"); r += test1(); System.out.println("test2"); r += test2(); System.out.println("test3"); r += test3(); System.out.println("test4"); r += test4(); System.out.println("test5"); r += test5(); System.out.println("test6"); r += test6(); System.out.println(r); } public static long test1() { long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { r += new Timestamp(System.currentTimeMillis()).getTime(); } return r; } public static long test2() { ArrayList l = new ArrayList(1000); for (int i = 0; i < 1000; ++i) { l.add(i); } long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { for (Iterator it = l.iterator(); it.hasNext(); ) { r += it.next().longValue(); } } return r; } public static long test3() { long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { Timestamp ts = new Timestamp(System.currentTimeMillis()); ts.setTime(42); r += ts.getTime(); } return r; } public static long test4() { ArrayList l = new ArrayList(1000); for (int i = 0; i < 1000; ++i) { l.add(i); } long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { Iterator it = l.iterator(); r += it.next().longValue(); r += it.next().longValue(); r += it.next().longValue(); r += it.next().longValue(); } return r; } public static long test5() { ArrayList l = new ArrayList(1000); for (int i = 0; i < 1000; ++i) { l.add(i); } long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { Iterator it = l.iterator(); for (int i = 0; i < l.size(); ++i) { r += it.next().longValue(); } } return r; } public static long test6() { long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start  0; ts.setTime(ts.getTime() + System.currentTimeMillis())) { r += ts.getTime(); } } return r; } } 

这是它在Linux上用Java 7输出的内容

 java -server -version java version "1.7.0_02" Java(TM) SE Runtime Environment (build 1.7.0_02-b13) Java HotSpot(TM) 64-Bit Server VM (build 22.0-b10, mixed mode) java -server -verbose:gc -XX:CompileThreshold=1 -cp bin EscapeAnalysis test1 test2 [GC 15616K->352K(59776K), 0,0014270 secs] [GC 15968K->288K(59776K), 0,0011790 secs] [GC 15904K->288K(59776K), 0,0018170 secs] [GC 15904K->288K(59776K), 0,0011100 secs] [GC 15904K->288K(57152K), 0,0019790 secs] [GC 15520K->320K(56896K), 0,0011670 secs] [GC 15232K->284K(56256K), 0,0011440 secs] test3 test4 test5 [GC 14876K->348K(55936K), 0,0005340 secs] [GC 14620K->348K(56000K), 0,0004560 secs] [GC 14300K->316K(55296K), 0,0004680 secs] [GC 13948K->316K(55488K), 0,0003590 secs] [GC 13692K->316K(54784K), 0,0004580 secs] [GC 13436K->316K(54976K), 0,0005430 secs] [GC 13180K->316K(54272K), 0,0004500 secs] [GC 12924K->316K(54464K), 0,0005090 secs] [GC 12668K->316K(53760K), 0,0004490 secs] [GC 12412K->316K(53888K), 0,0004350 secs] [GC 12156K->316K(53312K), 0,0005060 secs] test6 6737499643744733086 

我正在使用GC日志来了解是否在堆栈上分配了对象(来自Java中的Escape分析的想法),这可能不是100%可靠但似乎给出了很好的提示。

根据输出,堆栈分配适用于test1,test3,test4和test6,不适用于test2和test5。 我不明白为什么这对for循环中的迭代器不起作用,尽管它有效

  • 使用for循环外的迭代器(参见test4),
  • 与for循环中的另一个对象(参见test6)。

我已经阅读了ArrayList迭代器的代码,我不明白为什么它不适合在测试2和5中进行堆栈分配,因为它既不会逃避当前方法也不会逃避当前线程。

任何想法?

EA是C2编译器基于它生成的IR分析的东西,因此您需要它才能在享受优势之前编译该方法。 每个测试只调用一次,因此没有机会进行编译。 热点内部维基中的EA和C2 IR的详细信息( https://wikis.oracle.com/display/HotSpotInternals/Overview+of+Ideal,+C2的+高+级+中间+表示和https:// wikis.oracle.com/display/HotSpotInternals/EscapeAnalysis )

这是一个试图显示影响的版本

 import com.sun.management.ThreadMXBean; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Iterator; public class EscapeAnalysisTest { private static final long TIME_TO_TEST = 10L * 1000L; // 10s static class Timestamp { private long millis; public Timestamp(long millis) { this.millis = millis; } public long getTime() { return millis; } public void setTime(long time) { millis = time; } } public static void main(String[] args) { System.out.println("****"); doIt(); System.out.println("****"); doIt(); System.out.println("****"); doIt(); System.out.println("****"); doIt(); System.out.println("****"); } private static void doIt() { final ThreadMXBean mxbean = (ThreadMXBean) ManagementFactory.getThreadMXBean(); final long tid = Thread.currentThread().getId(); long r = 0; final long allocPre = mxbean.getThreadAllocatedBytes(tid); r += test1(); long alloc1 = mxbean.getThreadAllocatedBytes(tid); System.out.println("test1 - " + (alloc1 - allocPre)); r += test2(); final long alloc2 = mxbean.getThreadAllocatedBytes(tid); System.out.println("test2 - " + (alloc2 - alloc1)); r += test3(); final long alloc3 = mxbean.getThreadAllocatedBytes(tid); System.out.println("test3 - " + (alloc3 - alloc2)); r += test4(); final long alloc4 = mxbean.getThreadAllocatedBytes(tid); System.out.println("test4 - " + (alloc4 - alloc3)); r += test5(); final long alloc5 = mxbean.getThreadAllocatedBytes(tid); System.out.println("test5 - " + (alloc5 - alloc4)); r += test6(); final long alloc6 = mxbean.getThreadAllocatedBytes(tid); System.out.println("test6 - " + (alloc6 - alloc5)); System.out.println(r); } public static long test1() { long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { r += new Timestamp(System.currentTimeMillis()).getTime(); } return r; } public static long test2() { ArrayList l = new ArrayList(1000); for (int i = 0; i < 1000; ++i) { l.add(i); } long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { for (Iterator it = l.iterator(); it.hasNext(); ) { r += it.next().longValue(); } } return r; } public static long test3() { long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { Timestamp ts = new Timestamp(System.currentTimeMillis()); ts.setTime(42); r += ts.getTime(); } return r; } public static long test4() { ArrayList l = new ArrayList(1000); for (int i = 0; i < 1000; ++i) { l.add(i); } long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { Iterator it = l.iterator(); r += it.next().longValue(); r += it.next().longValue(); r += it.next().longValue(); r += it.next().longValue(); } return r; } public static long test5() { ArrayList l = new ArrayList(1000); for (int i = 0; i < 1000; ++i) { l.add(i); } long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { Iterator it = l.iterator(); for (int i = 0; i < l.size(); ++i) { r += it.next().longValue(); } } return r; } public static long test6() { long r = 0; long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < TIME_TO_TEST) { for (Timestamp ts = new Timestamp(System.currentTi()); ts.getTime() > 0; ts.setTime(ts.getTime() + System.currentTimeMillis())) { r += ts.getTime(); } } return r; } } 

使用-server -XX:CompileThreshold=1运行时生成以下输出-server -XX:CompileThreshold=1

 **** test1 - 109048 test2 - 89243416 test3 - 16664 test4 - 42840 test5 - 71982168 test6 - 1400 -5351026995119026839 **** test1 - 16432 test2 - 85921464 test3 - 16664 test4 - 42840 test5 - 66777600 test6 - 1368 7844020592566674506 **** test1 - 48 test2 - 18256 test3 - 272 test4 - 18264 test5 - 18264 test6 - 272 -2137858376905291730 **** test1 - 48 test2 - 18256 test3 - 272 test4 - 18264 test5 - 18264 test6 - 272 3273987624143297143 **** 

这里的一个危险就是这个方法的编译从根本上改变了它,我没有试图防止这种情况,因此可能需要使用LogCompilationPrintCompilation进行检查。

Escape Analysis严重依赖于函数调用的内联。

与任何其他微基准测试一样 – 特别是在服务器VM上 – 需要预热。 如果删除-XX:CompileThreshold=1并在循环中执行主测试,您将注意到在1-2次迭代后它将停止收集垃圾,因为编译器收集了足够的分析信息以内联方法,然后执行转义分析。

我刚刚调查了同样的事情,但对于Java 8.我把答案放在一个重复的问题中,因为我没有及时找到这个。

完整答案摘要:

首先,它依赖于实现。 这个答案适用于OpenJDK 1.8,也可能适用于Oracle JVM 1.8。

其次,正如其他人所说,堆栈分配仅在C2编译器编译方法时发生,只有在方法被调用足够次数时才会发生。

如果是这样,对象可以堆栈分配,如果

  • 所有使用它的方法调用都是内联的
  • 它从不分配给任何静态或对象字段,仅分配给局部变量(内联方法调用的参数变为局部变量)
  • 在程序的每个点,哪些局部变量包含对象的引用必须是JIT时间可确定的,并且不依赖于任何不可预测的条件控制流。
  • 如果对象是数组,则其大小必须是JIT时间常量,并且索引必须使用JIT时间常量。

如果您不了解Hotspot的某些特定怪癖,那么内联尤其是不可预测的。 有关详细信息,请参阅链接的答案。

编辑:我尝试在java 8(OpenJDK)上运行测试,所有内容都在那里内联。 因此,java 7和8之间的堆栈分配存在差异。

Interesting Posts