Java 热点何时可以在堆栈上分配对象?

Java 热点何时可以在堆栈上分配对象?,java,jvm,compiler-optimization,jvm-hotspot,stack-allocation,Java,Jvm,Compiler Optimization,Jvm Hotspot,Stack Allocation,因为在Java6附近,Hotspot JVM可以进行转义分析,并在堆栈上而不是在垃圾收集堆上分配非转义对象。这将导致生成代码的加速,并减少垃圾收集器的压力 Hotspot能够堆叠和分配对象的规则是什么?换句话说,我什么时候可以依靠它进行堆栈分配 编辑:这个问题是重复的,但(依我看)下面的答案比原始问题的答案更好。我做了一些实验,以查看Hotspot何时能够进行堆栈分配。事实证明,它的堆栈分配比基于。Choi的参考论文“Escape Analysis for Java”建议,只分配给局部变量的对象

因为在Java6附近,Hotspot JVM可以进行转义分析,并在堆栈上而不是在垃圾收集堆上分配非转义对象。这将导致生成代码的加速,并减少垃圾收集器的压力

Hotspot能够堆叠和分配对象的规则是什么?换句话说,我什么时候可以依靠它进行堆栈分配


编辑:这个问题是重复的,但(依我看)下面的答案比原始问题的答案更好。

我做了一些实验,以查看Hotspot何时能够进行堆栈分配。事实证明,它的堆栈分配比基于。Choi的参考论文“Escape Analysis for Java”建议,只分配给局部变量的对象始终可以进行堆栈分配。但事实并非如此

所有这些都是当前热点实现的实现细节,因此它们可能会在未来的版本中更改。这是指我的OpenJDK安装,它是针对X86-64的1.8.0_121版本

基于大量实验的简短总结似乎是:

如果

  • 它的所有用途都是内联的
  • 它从不分配给任何静态或对象字段,只分配给局部变量
  • 在程序中的每个点,哪些局部变量包含对对象的引用必须是JIT时间可确定的,并且不依赖于任何不可预测的条件控制流。
  • 如果对象是数组,则其大小必须在JIT时间已知,并且对其进行索引必须使用JIT时间常数
要知道这些条件何时成立,您需要了解Hotspot是如何工作的。在特定情况下,依靠Hotspot来确定地进行堆栈分配可能是有风险的,因为其中涉及很多非局部因素。尤其是知道是否一切都是内联的可能很难预测

实际上,如果您只是使用简单迭代器进行迭代,那么它们通常是可分配堆栈的。对于复合对象,只有外部对象可以被堆栈分配,因此列表和其他集合总是导致堆分配

如果您有一个
HashMap
并在
myHashMap.get(42)
中使用它,那么
42
可能会在测试程序中进行堆栈分配,但它不会在完整的应用程序中,因为您可以确定在整个程序的HashMap中有两种以上类型的键对象,因此,键上的hashCode和equals方法不会内联

除此之外,我没有看到任何普遍适用的规则,这将取决于代码的细节

热点内部 要知道的第一件重要事情是,在内联之后执行逃逸分析。这意味着Hotspot的转义分析在这方面比Choi论文中的描述更强大,因为从方法返回但调用方方法本地的对象仍然可以被堆栈分配。因此,如果您这样做,迭代器几乎总是可以被堆栈分配的,例如,
for(Foo-item:myList){…}
(而且
myList.iterator()
的实现非常简单,它们通常是这样的。)

Hotspot仅在确定方法为“热”时编译方法的优化版本,因此未多次运行的代码根本不会得到优化,在这种情况下,没有任何堆栈分配或内联。但是对于那些方法,你通常不在乎

内联 内联决策基于Hotspot首先收集的分析数据。声明的类型并不重要,即使方法是虚拟的,Hotspot也可以基于在分析期间看到的对象类型将其内联。类似的情况也适用于分支(即if语句和其他控制流构造):如果在分析期间hospot从未看到某个分支被执行,它将基于从未执行该分支的假设编译和优化代码。在这两种情况下,如果Hotspot无法证明其假设总是正确的,它将在编译代码中插入称为“不常见陷阱”的检查,如果遇到此类陷阱,Hotspot将根据新信息进行去优化,并可能重新优化

Hotspot将分析哪些对象类型作为接收器出现在哪些调用站点。如果Hotspot在一个调用站点上只看到一个类型或两个不同的类型,它就能够内联被调用的方法。如果只有一个或两个非常常见的类型,而其他类型出现的频率要低得多,那么Hotspot还应该能够内联常见类型的方法,包括检查它需要执行哪些代码。(我不完全确定最后一个案例有一两种常见类型和更多不常见类型)。如果有两种以上的常见类型,Hotspot将根本不内联调用,而是为间接调用生成机器代码

这里的“类型”指的是对象的确切类型。未考虑实现的接口或共享超类。即使调用站点上出现不同的接收器类型,但它们都继承了方法的相同实现(例如,多个类都从
对象
继承
hashCode
),Hotspot仍将生成间接调用,而不是内联调用。(因此,在这种情况下,i.m.o.hotspot是相当愚蠢的。我希望未来的版本能够改进这一点。)

Hotspot也只会内联不太大的方法“不太大”由
-XX:MaxInlineSize=n
-XX:frequinlinesize=n
选项决定。JVM字节码大小小于MaxInlineSize的可内联方法始终是内联的,如果调用为“热”,则JVM字节码大小小于FrequeInlineSize的方法是内联的。较大的方法从不内联。由d
static <T,U> List<U> map(List<T> list, Function<T,U> func) {
    List<U> result = new ArrayList();
    for(T item : list) { result.add(func.call(item)); }
    return result; 
}
// Minimal example for which the JVM does not scalarize the allocation. If field is final, or the second allocation is unconditional, it will.

class Scalarization {

        int field = 0xbd;
        long foo(long i) { return i * field; }


        public static void main(String[] args) {
                long result = 0;
                for(long i=0; i<100; i++) {
                        result += test();
                }
                System.out.println("Result: "+result);
        }


        static long test() {
                long ctr = 0x5;
                for(long i=0; i<0x10000; i++) {

                Scalarization s = new Scalarization();
                ctr = s.foo(ctr);
                if(i == 0) s = new Scalarization();
                ctr = s.foo(ctr);
                }
                return ctr;
        }
}
Foo f = new Foo();
bar.field = f;
long foo(long i) { return i * 0xbb; }

static long test() {
    long ctr = 0x5;
    for(long i=0; i<0x10000; i++) {
        Scalarization s = new Scalarization();
        ctr = s.foo(ctr);
        if(i == 50) s = new Scalarization();
        ctr = s.foo(ctr);
    }
    return ctr;
}
static long test() {
    long ctr = 0x5;
    for(long i=0; i<0x10000; i++) {
        Scalarization s = new Scalarization();
        ctr = s.foo(ctr);
        s = new Scalarization();
        ctr = s.foo(ctr);
    }
    return ctr;
}
static long limit = 0;

static long test() {
    long ctr = 0x5;
    long i = limit;
    limit += 0x10000;
    for(; i<limit; i++) { // In this form if scalarization happens is nondeterministic: if the condition is hit before profiling starts scalarization happens, else not.

        Scalarization s = new Scalarization();
        ctr = s.foo(ctr);
        if(i == 0xf9a0) s = new Scalarization();
        ctr = s.foo(ctr);
    }
    return ctr;
}