是否总是计算返回表达式,还是可以由编译器优化它?

时间:2017-02-05 00:19:00

标签: java garbage-collection javac

我知道在这段代码中:

public static void main(String[] args) {myMethod();}
private Object myMethod() {
    Object o = new Object();
    return o;
}

垃圾收集器会在执行o后销毁myMethod,因为myMethod的返回值未分配,因此没有对它的引用。但是如果代码是这样的话会怎么样:

public static void main(String[] args) {myMethod();}
private Object myMethod() {
    int i = 5;
    return i + 10;
}

编译器是否会麻烦处理i + 10,因为没有分配返回值?

如果i不是简单的原语,而是更大的对象:

public static void main(String[] args) {myMethod();}
private Object myMethod() {
    return new LargeObject();
}

其中LargeObject有一个昂贵的构造函数,编译器是否仍会分配内存并调用构造函数,以防它有任何副作用?

如果返回表达式很复杂但没有副作用,这一点尤其重要,例如:

public static void main(String[] args) {
    List<Integer> list = new LinkedList();
    getMiddle();
}
private Object getMiddle(List list) {
    return list.get((int) list(size) / 2);
}

在不使用返回值的情况下在现实生活中调用此方法将毫无意义,但这只是为了举例。

我的问题是:给定这些示例(对象构造函数,对基元的操作,没有副作用的方法调用),如果编译器看到该值未被赋值给它,则可以跳过方法的return语句什么?

我知道我可以针对这些问题进行多次测试,但我不知道我是否会信任他们。我对代码优化和GC的理解是相当基础的,但我想我已经足够了解,特定代码位的处理并不一定是可以推广的。这就是我要问的原因。

2 个答案:

答案 0 :(得分:2)

首先,让我们来处理你的问题和一些评论中明显的误解。

在HotSpot(Oracle或OpenJDK)Java平台中,实际上有两个必须考虑的编译器:

  • javac编译器将Java源代码转换为字节码。它做了最小化的优化。事实上,它所做的唯一重要的优化是评估编译时常量表达式(这实际上是某些编译时检查所必需的)和重写字符串连接序列。

    您可以轻松查看已完成的优化...使用javap ...但它也会产生误导,因为重负荷优化尚未完成。基本上,javap输出在优化方面几乎没有帮助。

  • JIT编译器执行重量级优化。它在程序运行时在运行时调用。

    不会立即调用它。通常,您的字节码最初几次被解释为调用任何方法。 JVM正在收集行为统计数据,JIT编译器将使用它来优化(!)。

因此,在您的示例中,main方法被调用一次,myMethd被调用一次。 JIT编译器甚至无法运行,因此实际上将解释字节码。 但这很酷。 JIT编译器优化的时间比通过运行优化器节省的时间要多。

但假设优化器确实运行了......

JIT代码编译器通常有几个策略:

  • 在方法中,它会根据方法的本地信息进行优化。
  • 调用方法时,它会查看被调用方法是否可以在呼叫站点内联。在内联之后,代码可以在其上下文中进一步优化。

所以可能会发生什么

  • 然后您的myMethod()被优化为一种独立的方法,不会对不必要的语句进行优化。因为在所有可能的情境中他们都没有必要。

  • 当/如果对myMethod()的方法调用内联时(例如,在main(...)方法中,优化器将确定(例如)这些语句

        int i = 5;
        return i + 10;
    

    在此上下文中是不必要的,并将其优化。

但请记住,JIT编译器一直在不断发展。因此,准确地预测将发生什么样的优化,以及何时,几乎不可能。也许没有结果。

建议:

  • 值得思考你是否在&#34;粗略的&#34;水平。选择正确的算法或数据结构通常很关键。

  • 在细粒度级别,通常不值得。让JIT编译器处理它。

    除非您有明确的证据表明您需要进行优化(即客观上太慢的基准测试),并明确证明特定点存在性能瓶颈(例如,分析结果)。

答案 1 :(得分:1)

诸如“编译器会做什么?”之类的问题。关于Java有点天真。首先,有两个编译器和一个解释器。静态编译器执行一些简单的优化,例如或许使用有效的最终操作数优化任何算术表达式。它当然将常量,文字和常量表达式编译成字节码文字。真正的魔法发生在运行时。

我认为没有理由为什么结果计算会被优化掉,除非忽略返回值。忽略返回值很少见,应该更少见。

在运行时,可以在上下文中获得更多信息。对于优化,运行时解释器和编译器动态二重奏可以解决诸如“这部分代码是否值得优化?”之类的问题。如果调用者使用返回值,HotSpot及其同类将不会优化return new Foo();实例化。但是他们可能会采用不同的方式,可能会在堆栈中抛出属性,甚至在情况允许的情况下抛出寄存器。因此,虽然对象存在于逻辑Java堆上,但它可能存在于物理JVM组件的其他位置。

谁知道具体的优化是否会发生?没有人。但他们或类似的东西,或更神奇的东西,可能会发生。可能HotSpot所执行的优化与我们期望或想象的不同并且更好,当它以其智慧决定采取麻烦进行优化时。

哦,在运行时,HotSpot可能会优先处理以前优化的代码。这是为了维护Java代码的语义。