JVM 解释和编译指南

2023年 11月 6日 66.5k 0

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

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论