Java 是一种跨平台的编程语言。程序源代码会被编译为 字节码bytecode,然后字节码在运行时被转换为 机器码machine code。解释器interpreter 在物理机器上模拟出的抽象计算机上执行字节码指令。即时just-in-time(JIT)编译发生在运行期,而 预先ahead-of-time(AOT)编译发生在构建期。
本文将说明解释器、JIT 和 AOT 分别何时起作用,以及如何在 JIT 和 AOT 之间权衡。
源代码、字节码、机器码
应用程序通常是由 C、C++ 或 Java 等编程语言编写。用这些高级编程语言编写的指令集合称为源代码。源代码是人类可读的。要在目标机器上执行它,需要将源代码转换为机器可读的机器码。这个转换工作通常是由 编译器compiler
然而,在 Java 中,源代码首先被转换为一种中间形式,称为字节码。字节码是平台无关的,所以 Java 被称为平台无关编程语言。Java 编译器 javac
将源代码转换为字节码。然后解释器解释执行字节码。
下面是一个简单的 Java 程序, Hello.java
:
//Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Inside Hello World!");
}
}
使用 javac
编译它,生成包含字节码的 Hello.class
文件。
$ javac Hello.java
$ ls
Hello.class Hello.java
现在,使用 javap
来反汇编 Hello.class
文件的内容。使用 javap
时如果不指定任何选项,它将打印基本信息,包括编译这个 .class
文件的源文件、包名称、公共和受保护字段以及类的方法。
$ javap Hello.class
Compiled from "Hello.java"
public class Hello {
public Hello();
public static void main(java.lang.String[]);
}
要查看 .class
文件中的字节码内容,使用 -c
选项:
$ javap -c Hello.class
Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Inside Hello World!
5: invokevirtual #4 // Method
java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
要获取更详细的信息,使用 -v
选项:
$ javap -v Hello.class
解释器,JIT 和 AOT
解释器负责在物理机器上模拟出的抽象计算机上执行字节码指令。当使用 javac
编译源代码,然后使用 java
执行时,解释器在程序运行时运行并完成它的目标。
$ javac Hello.java
$ java Hello
Inside Hello World!
JIT 编译器也在运行期发挥作用。当解释器解释 Java 程序时,另一个称为运行时 分析器profiler 的组件将静默地监视程序的执行,统计各部分代码被解释的次数。基于这些统计信息可以检测出程序的 热点hotspot,即那些经常被解释的代码。一旦代码被解释次数超过设定的阈值,它们满足被
JIT 编译器直接转换为机器码的条件。所以 JIT
编译器也被称为分析优化的编译器。从字节码到机器码的转换是在程序运行过程中进行的,因此称为即时编译。JIT
减少了解释器将同一组指令模拟为机器码的负担。
AOT 编译器在构建期编译代码。在构建时将需要频繁解释和 JIT 编译的代码直接编译为机器码可以缩短 Java 虚拟机Java Virtual Machine(JVM) 的预热warm-up时间。(LCTT
译注:Java 程序启动后首先字节码被解释执行,此时执行效率较低。等到程序运行了足够的时间后,代码热点被检测出来,JIT
开始发挥作用,程序运行效率提升。JIT 发挥作用之前的过程就是预热。)AOT 是在 Java 9 中引入的一个实验性特性。jaotc
使用 Graal 编译器(它本身也是用 Java 编写的)来实现 AOT 编译。
以 Hello.java
为例:
//Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Inside Hello World!");
}
}
$ javac Hello.java
$ jaotc --output libHello.so Hello.class
$ java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=./libHello.so Hello
Inside Hello World!
解释和编译发生的时机
下面通过例子来展示 Java 在什么时候使用解释器,以及 JIT 和 AOT 何时参与进来。这里有一个简单的程序 Demo.java
:
//Demo.java
public class Demo {
public int square(int i) throws Exception {
return(i*i);
}
public static void main(String[] args) throws Exception {
for (int i = 1; i