1. 简介

在本文中,我们将介绍 Java 的提前编译(Ahead-of-Time Compilation,简称 AOT)。这项技术在 JEP-295 中被定义,并作为实验性功能首次引入 Java 9。

我们将从以下几个方面展开:

  1. 什么是 AOT 编译;
  2. 通过一个简单的示例展示 AOT 的使用;
  3. AOT 的一些限制;
  4. AOT 的典型应用场景。

2. 什么是 AOT 编译?

AOT 编译是一种用于提升 Java 程序性能,特别是 JVM 启动速度的技术

JVM 默认执行的是 Java 字节码,并在运行时将热点代码编译为本地机器码,这个过程称为即时编译(JIT)。JIT 的优势在于可以根据运行时信息进行高度优化,从而提升程序峰值性能。

❌ 但 JIT 的缺点也很明显:程序启动初期性能较差,因为此时热点代码尚未被编译

💡 AOT 编译的目的就是解决这个“预热期”问题。它在程序运行前就将字节码编译为本地代码,从而缩短启动时间。

⚠️ AOT 编译器使用的是 Graal 编译器。关于 JIT 和 Graal 的更多细节,可以参考我们其他文章:

3. 示例演示

我们通过一个简单的 Java 类来演示 AOT 的使用。

3.1. AOT 编译示例类

public class JaotCompilation {

    public static void main(String[] argv) {
        System.out.println(message());
    }

    public static String message() {
        return "The JAOT compiler says 'Hello'";
    }
}

首先,使用 javac 编译该类:

javac JaotCompilation.java

然后使用 jaotc 编译器生成本地库文件:

jaotc --output jaotCompilation.so JaotCompilation.class

这会生成一个名为 jaotCompilation.so 的本地共享库文件。

3.2. 运行程序

执行时,通过 -XX:AOTLibrary 参数加载 AOT 库:

java -XX:AOTLibrary=./jaotCompilation.so JaotCompilation

也可以将库文件拷贝到 $JAVA_HOME/lib 目录下,然后直接使用库名。

3.3. 验证库是否被使用

添加 -XX:+PrintAOT 参数可以查看库是否被加载:

java -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so JaotCompilation

输出示例:

77    1     loaded    ./jaotCompilation.so  aot library

如果加上 -verbose 参数,可以看到具体方法是否被调用:

java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation

输出示例:

11    1     loaded    ./jaotCompilation.so  aot library
116    1     aot[ 1]   jaotc.JaotCompilation.<init>()V
116    2     aot[ 1]   jaotc.JaotCompilation.message()Ljava/lang/String;
116    3     aot[ 1]   jaotc.JaotCompilation.main([Ljava/lang/String;)V
The JAOT compiler says 'Hello'

⚠️ AOT 库中包含类的“指纹”,必须与 .class 文件一致,否则不会使用 AOT 代码

如果我们修改了类代码,例如:

public static String message() {
    return "The JAOT compiler says 'Good morning'";
}

但未重新编译 AOT 库,运行时将不使用 AOT 库中的方法:

11 1 loaded ./jaotCompilation.so aot library
The JAOT compiler says 'Good morning'

4. 更多 AOT 与 JVM 参数

4.1. 编译 Java 模块

我们可以对整个模块进行 AOT 编译:

jaotc --output javaBase.so --module java.base

生成的库文件通常很大(约 320MB),可以通过指定包或类来减小体积。

4.2. 使用编译命令进行选择性编译

为避免库文件过大,可以使用编译命令文件限制编译范围。例如创建 compileCommands.txt 文件:

compileOnly java.lang.*

然后使用:

jaotc --output javaBaseLang.so --module java.base --compile-commands compileCommands.txt

生成的库只包含 java.lang 包下的类。

💡 要提升性能,可以通过以下参数获取 JVM 预热时调用的类:

java -XX:+UnlockDiagnosticVMOptions -XX:+LogTouchedMethods -XX:+PrintTouchedMethodsAtExit JaotCompilation

4.3. 编译单个类

jaotc --output javaBaseString.so --class-name java.lang.String

仅编译 String 类。

4.4. 编译支持 Tiered 模式

默认情况下,AOT 编译的代码始终使用,不会进行 JIT。如果希望支持 JIT,可以添加 --compile-for-tiered 参数:

jaotc --output jaotCompilation.so --compile-for-tiered JaotCompilation.class

此时,AOT 代码将被使用直到 JIT 编译就绪。

5. AOT 编译的典型使用场景

适用于启动时间敏感的短生命周期程序,如命令行工具、脚本等。

嵌入式系统或资源受限环境,JIT 无法使用时 AOT 是一个备选方案。

⚠️ AOT 库只能被字节码完全一致的类加载,不能通过 JNI 加载。

6. AOT 与 AWS Lambda

✅ AOT 编译非常适合 AWS Lambda 函数,尤其是对启动时间敏感的场景。

⚠️ 必须在与 AWS Lambda 相同的操作系统(如 Amazon Linux 2)上构建 AOT 库

6.1. 开发环境配置

使用 Docker 搭建 Amazon Linux 2 + Amazon Corretto 环境:

docker pull amazonlinux
yum install java-11-amazon-corretto
yum install binutils.x86_64

6.2. 编译类与库

在容器中编译并生成库:

mkdir aot && cd aot
mkdir jaotc && cd jaotc

# JaotCompilation.java 内容如下:
package jaotc;
public class JaotCompilation {
    public static int message(int input) {
        return input * 2;
    }
}

javac JaotCompilation.java
cd ..
jaotc -J-XX:+UseSerialGC --output jaotCompilation.so jaotc/JaotCompilation.class
zip -r jaot.zip jaotCompilation.so jaotc/

⚠️ 请确保使用与 AWS Lambda 相同的 GC(如 SerialGC)。

6.3. 配置 AWS Lambda

上传 jaot.zip 并设置如下参数:

  • Runtime: Java 11
  • Handler: jaotc.JaotCompilation::message
  • Environment Variable:
    • Name: JAVA_TOOL_OPTIONS
    • Value: -XX:+UnlockExperimentalVMOptions -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so

输入测试数据为字符串 "1",执行后可在日志中看到:

57    1     loaded    ./jaotCompilation.so  aot library

7. 总结

本文我们演示了如何使用 jaotc 对 Java 类和模块进行 AOT 编译,并探讨了其在 Lambda 等场景中的应用。

⚠️ AOT 目前仍为实验性功能,不是所有 Java 发行版都默认包含,实际应用案例也较为稀少。未来能否成为主流,还需要社区进一步探索和实践。


原始标题:Ahead of Time Compilation (AoT) | Baeldung