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

2022-09-03 18:01:35

让我们想象一下,我们有以下类:

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编译器决定背后的基本原理是什么?


答案 1

Java 编译器会剥离语句和 ?ab

(源到字节码)编译器不会剥离任何一个调用。(通过检查字节码可以很容易地检查这一点;例如,查看输出。javacjavap -c

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

符合JLS:-)。

从务实的角度来看:

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

    • Message已编译,
    • 主类被编译,
    • Message经过编辑,以便执行某些操作...并重新编译。log

    现在我们有一个编译不正确的主类,它没有做正确的事情,因为过早内联的代码已经过时了。ab


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

  • 如果 JIT 编译器可以推断出不需要虚拟方法分派,则该方法将传入并内联。(If 是应用程序使用的唯一一个类,它实现了这一点,对于一个好的 JIT 编译器来说,这是一个明智的选择。abLoggerILogger

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

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

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


答案 2

反汇编以下文件(with )表明在编译为字节码时,1.7.0 编译器不会将它们剥离出来:javap -c

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)
    }
}

结果如下。密钥位是第 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
}

编辑:但是,正如@mikera指出的那样,JIT编译器可能会在程序运行时进行进一步的优化,这可能能够消除调用。不幸的是,我对细节的了解还不够多,无法对此发表评论。

附注:您可能对此链接感兴趣,该链接涉及热点JVM使用的性能技术:

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


推荐