Java:JVM将如何优化对void函数的调用?


问题内容

假设我们有以下类:

public class Message extends Object {}

public class Logger implements ILogger {
 public void log(Message m) {/*empty*/}
}

和以下程序:

public static void main(String args[]) {
  ILogger l = new Logger();
  l.log((Message)null); // a)
  l.log(new Message()); // b)
}

Java编译器是否会去除语句 ab ?在这两种情况下(剥离还是不剥离),Java编译器决定背后的原理是什么?


问题答案:

Java编译器将去掉语句ab

javac(源到字节码)编译器不会破坏任何呼叫。(通过检查字节码很容易检查这一点;例如,查看javap -c输出。)

在这两种情况下(剥离还是不剥离),Java编译器决定背后的原理是什么?

符合JLS :-)。

从务实的角度:

  • 如果javac编译器优化了这些调用,则Java调试器将根本看不到它们,这对于开发人员来说会造成混乱。
  • javac如果Message类和主类是独立编译/修改的,则早期优化(by )将导致破坏。例如,考虑以下顺序:

    • Message 被编译,
    • 主类被编译,
    • Message进行了编辑,以便log执行某些操作并重新编译。

现在我们有一个不正确编译主类,没有做的事情,在ab由于过早内联代码是过时。


但是,JIT编译器 可能 以多种方式 在运行时 优化代码。例如:

  • 该方法要求在ab可被内联如果JIT编译器可以推断出没有虚拟方法调度是必需的。(If Logger是应用程序使用的唯一实现该类的类,ILogger对于优质的JIT编译器而言,这是不言而喻的。)

  • 内联第一个方法调用后,JIT编译器可以确定主体为noop并优化调用。

  • 在第二个方法调用的情况下,JIT编译器可以进一步(通过转义分析)推断出Message不需要在堆上分配对象,或者根本不需要分配对象。

(如果您想知道(您的平台上的)JIT编译器的 实际 作用,Hotspot JVM具有一个JVM选项,该选项会为选定的方法转储JIT编译的本机代码。)