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

Dav*_*tti 12 java jvm javac

我们假设我们有以下类:

public class Message extends Object {}

public class Logger implements ILogger {
 public void log(Message m) {/*empty*/}
}
Run Code Online (Sandbox Code Playgroud)

以及以下程序:

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

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

Ste*_*n C 17

Java编译器将去掉语句ab

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

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

符合JLS :-).

从务实的角度来看:

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

    • Message 编译,
    • 主类是编译的,
    • Message编辑,以便log做某事......并重新编译.

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


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

  • 如果JIT编译器可以推断出不需要虚拟方法调度,则该方法可以调用a并且b可以内联.(如果Logger是应用程序使用的唯一一个类ILogger,它可以很好地为JIT编译器实现这一点.)

  • 在内联第一个方法调用之后,JIT编译器可以确定主体是noop并优化呼叫.

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

(如果您想知道JIT编译器(在您的平台上)实际上做了什么,Hotspot JVM有一个JVM选项,可以为所选方法转储JIT编译的本机代码.)

  • +1这比我的答案要好得多. (2认同)

Stu*_*etz 6

反汇编以下文件(with javap -c)表明在编译为字节码时1.7.0编译器不会删除它们:

public class Program
{
    public static class Message extends Object {}

    public interface ILogger {
        void log(Message m);
    }

    public static 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

结果如下.关键位是第13和26行的调用.

Compiled from "Program.java"
public class Program {
  public Program();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Program$Logger
       3: dup
       4: invokespecial #3                  // Method Program$Logger."<init>":()V
       7: astore_1
       8: aload_1
       9: aconst_null
      10: checkcast     #4                  // class Program$Message
      13: invokeinterface #5,  2            // InterfaceMethod Program$ILogger.log:(LProgram$Message;)V
      18: aload_1
      19: new           #4                  // class Program$Message
      22: dup
      23: invokespecial #6                  // Method Program$Message."<init>":()V
      26: invokeinterface #5,  2            // InterfaceMethod Program$ILogger.log:(LProgram$Message;)V
      31: return
}
Run Code Online (Sandbox Code Playgroud)

编辑:但是,正如@mikera指出的那样,JIT编译器可能会在程序运行时进行进一步的优化,这可能会消除调用.遗憾的是,我对细节的评论不够充分.

侧面注意:您可能对此链接感兴趣,该链接涉及Hotspot JVM使用的性能技术:

https://wikis.oracle.com/display/HotSpotInternals/PerformanceTechniques

  • 用于进行反汇编的+1,但请注意,JIT编译器可能在此阶段之后进行实际消除.....所以它实际上并不能证明这些调用是否最终得到优化. (3认同)