假设我们有以下类:
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 编译器会删除语句 a 和 b 吗?在这两种情况下(剥离或不剥离),Java 编译器的决定背后的基本原理是什么?
最佳答案
Will the Java compiler strip out statements
a
andb
?
javac
(源代码到字节码)编译器不会删除任何一个调用。 (通过检查字节码很容易检查这一点;例如,查看 javap -c
输出。)
In both cases (stripping or not stripping), what is the rationale behind the Java compiler's decision ?
符合 JLS :-)。
从务实的角度来看:
- 如果
javac
编译器优化了调用,Java 调试器将根本看不到它们……这会让开发人员感到困惑。 如果
Message
类和主类是独立编译/修改的,早期优化(通过javac
)将导致破坏。例如,考虑这个序列:Message
被编译,- 主类编译完成,
Message
被编辑,以便log
做一些事情......并重新编译。
现在我们有一个编译错误的主类,它在
a
和b
处没有做正确的事情,因为过早内联的代码已经过时了。
但是,JIT 编译器可能以多种方式在运行时优化代码。例如:
如果 JIT 编译器可以推断不需要虚拟方法调度,则可以内联
a
和b
中的方法调用。 (如果Logger
是实现ILogger
的应用程序使用的唯一类,那么这对于一个好的 JIT 编译器来说是不费吹灰之力的。)内联第一个方法调用后,JIT 编译器可能会确定主体是 noop 并优化调用。
在第二个方法调用的情况下,JIT 编译器可以进一步推断(通过逃逸分析)
Message
对象不需要在堆上分配...或者根本没有。
(如果您想知道 JIT 编译器(在您的平台上)实际上做了什么,Hotspot JVM 有一个 JVM 选项,可以为选定的方法转储 JIT 编译的 native 代码。)
关于Java:JVM 将如何优化对 void 和空函数的调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14207401/