Ahead of Time Compilation (AoT) – 时代前线汇编 (AoT)

最后修改: 2019年 2月 18日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this article, we’ll look at the Java Ahead of Time (AOT) Compiler, which is described in JEP-295 and was added as an experimental feature in Java 9.

在本文中,我们将了解 Java AOT 编译器,它在JEP-295中有所描述,并在 Java 9 中作为一项实验性功能被添加。

First, we’ll see what AOT is, and second, we’ll look at a simple example. Third, we’ll see some restrictions of AOT, and lastly, we’ll discuss some possible use cases.

首先,我们将看到什么是AOT,其次,我们将看一个简单的例子。第三,我们将看到AOT的一些限制,最后,我们将讨论一些可能的用例。

2. What Is Ahead of Time Compilation?

2.什么是超前编译?

AOT compilation is one way of improving the performance of Java programs and in particular the startup time of the JVM. The JVM executes Java bytecode and compiles frequently executed code to native code. This is called Just-in-Time (JIT) Compilation. The JVM decides which code to JIT compile based on profiling information collected during execution.

AOT编译是提高Java程序性能的一种方式,特别是JVM的启动时间。JVM执行Java字节码,并将经常执行的代码编译为本地代码。这被称为 “即时编译(JIT)”。JVM根据执行过程中收集的剖析信息来决定哪些代码要进行JIT编译。

While this technique enables the JVM to produce highly optimized code and improves peak performance, the startup time is likely not optimal, as the executed code is not yet JIT compiled. AOT aims to improve this so-called warming-up period. The compiler used for AOT is Graal.

虽然这种技术能够使JVM产生高度优化的代码,并提高峰值性能,但由于所执行的代码尚未经过JIT编译,因此启动时间可能并不理想。AOT旨在改善这个所谓的暖机期。AOT使用的编译器是Graal。

In this article, we won’t look at JIT and Graal in detail. Please refer to our other articles for an overview of performance improvements in Java 9 and 10, as well as a deep dive into the Graal JIT Compiler.

在本文中,我们不会详细介绍 JIT 和 Graal。请参考我们的其他文章,了解Java 9 和 10 的性能改进,以及深入了解 Graal JIT 编译器

3. Example

3.例子

For this example, we’ll use a very simple class, compile it, and see how to use the resulting library.

在这个例子中,我们将使用一个非常简单的类,对它进行编译,并看看如何使用产生的库。

3.1. AOT Compilation

3.1.AOT编译

Let’s take a quick look at our sample class:

让我们快速浏览一下我们的样本类。

public class JaotCompilation {

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

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

Before we can use the AOT compiler, we need to compile the class with the Java compiler:

在我们使用AOT编译器之前,我们需要用Java编译器来编译这个类。

javac JaotCompilation.java

We then pass the resulting JaotCompilation.class to the AOT compiler, which is located in the same directory as the standard Java compiler:

然后我们将产生的JaotCompilation.class传递给AOT编译器,它与标准Java编译器位于同一目录。

jaotc --output jaotCompilation.so JaotCompilation.class

This produces the library jaotCompilation.so in the current directory.

这将在当前目录下产生库jaotCompilation.so

3.2. Running the Program

3.2.运行该程序

We can then execute the program:

然后我们可以执行该程序。

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

The argument -XX:AOTLibrary accepts a relative or full path to the library. Alternatively, we can copy the library to the lib folder in the Java home directory and only pass the name of the library.

参数-XX:AOTLibrary接受库的相对或全路径。另外,我们可以把库复制到Java主目录下的lib文件夹,只传递库的名称。

3.3. Verifying That the Library Is Called and Used

3.3.验证该库是否被调用和使用

We can see that the library was indeed loaded by adding -XX:+PrintAOT as a JVM argument:

我们可以看到,通过添加-XX:+PrintAOT作为JVM参数,该库确实被加载。

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

The output will look like:

输出将看起来像。

77    1     loaded    ./jaotCompilation.so  aot library

However, this only tells us that the library was loaded, but not that it was actually used. By passing the argument -verbose, we can see that the methods in the library are indeed called:

然而,这只告诉我们库被加载了,但没有告诉我们它被实际使用了。通过传递参数-verbose,我们可以看到库中的方法确实被调用。

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

The output will contain the lines:

输出将包含这几行字。

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'

The AOT compiled library contains a class fingerprint, which must match the fingerprint of the .class file.

AOT编译库包含一个类指纹,它必须与.class文件的指纹匹配。

Let’s change the code in the class JaotCompilation.java to return a different message:

让我们改变类JaotCompilation.java中的代码,以返回一个不同的消息。

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

If we execute the program without AOT compiling the modified class:

如果我们在没有AOT编译修改后的类的情况下执行该程序。

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

Then the output will contain only:

那么输出将只包含。

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

We can see that the methods in the library won’t be called, as the bytecode of the class has changed. The idea behind this is that the program will always produce the same result, no matter if an AOT compiled library is loaded or not.

我们可以看到,库中的方法不会被调用,因为类的字节码已经改变了。这背后的想法是,无论是否加载了AOT编译的库,程序都会产生相同的结果。

4. More AOT and JVM Arguments

4.更多的AOT和JVM参数

4.1. AOT Compilation of Java Modules

4.1.Java模块的AOT编译

It’s also possible to AOT compile a module:

也可以用AOT来编译一个模块。

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

The resulting library javaBase.so is about 320 MB in size and takes some time to load. The size can be reduced by selecting the packages and classes to be AOT compiled.

由此产生的库javaBase.so的大小约为320MB,需要一些时间来加载。可以通过选择要进行AOT编译的包和类来减少其大小。

We’ll look at how to do that below, however, we’ll not dive deeply into all the details.

我们将在下面看看如何做到这一点,然而,我们不会深入研究所有的细节。

4.2. Selective Compilation with Compile Commands

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

To prevent the AOT compiled library of a Java module from becoming too large, we can add compile commands to limit the scope of what gets AOT compiled. These commands need to be in a text file – in our example, we’ll use the file complileCommands.txt:

为了防止Java模块的AOT编译库变得过大,我们可以添加编译命令来限制被AOT编译的范围。这些命令需要放在一个文本文件中 – 在我们的例子中,我们将使用文件complileCommands.txt

compileOnly java.lang.*

Then, we add it to the compile command:

然后,我们把它添加到编译命令中。

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

The resulting library will only contain the AOT compiled classes in the package java.lang.

产生的库将只包含AOT编译的package java.lang中的类。

To gain real performance improvement, we need to find out which classes are invoked during the warm-up of the JVM.

为了获得真正的性能改进,我们需要找出哪些类在JVM的预热期间被调用

This can be achieved by adding several JVM arguments:

这可以通过添加几个JVM参数来实现。

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

In this article, we won’t dive deeper into this technique.

在这篇文章中,我们不会深入研究这种技术。

4.3. AOT Compilation of a Single Class

4.3.单个类的AOT编译

We can compile a single class with the argument –class-name:

我们可以用参数-class-name来编译一个单一的类。

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

The resulting library will only contain the class String.

产生的库将只包含String类。

4.4. Compile for Tiered

4.4.为分层编译

By default, the AOT compiled code will always be used, and no JIT compilation will happen for the classes included in the library. If we want to include the profiling information in the library, we can add the argument compile-for-tiered:

默认情况下,将始终使用AOT编译的代码,对于包含在库中的类,将不会发生JIT编译。如果我们想在库中包含剖析信息,我们可以添加参数compile-for-tiered

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

The pre-compiled code in the library will be used until the bytecode becomes eligible for JIT compilation.

库中的预编译代码将被使用,直到字节码有资格进行JIT编译。

5. Possible Use Cases for AOT Compilation

5.AOT编译的可能用例

One use case for AOT is short running programs, which finish execution before any JIT compilation occurs.

AOT的一个用例是短期运行的程序,在任何JIT编译发生之前完成执行。

Another use case is embedded environments, where JIT isn’t possible.

另一个用例是嵌入式环境,那里不可能有JIT。

At this point, we also need to note that the AOT compiled library can only be loaded from a Java class with identical bytecode, thus it cannot be loaded via JNI.

在这一点上,我们还需要注意,AOT编译库只能从具有相同字节码的Java类中加载,因此它不能通过JNI加载。

6. AOT and Amazon Lambda

6.AOT和亚马逊Lambda

A possible use case for AOT-compiled code is short-lived lambda functions where short startup time is important. In this section, we’ll look at how we can run AOT compiled Java code on AWS Lambda.

AOT编译的代码的一个可能的用例是短命的lambda函数,其中短的启动时间很重要。在本节中,我们将看看如何在AWS Lambda上运行AOT编译的Java代码。

Using AOT compilation with AWS Lambda requires the library to be built on an operating system that is compatible with the operating system used on AWS. At the time of writing, this is Amazon Linux 2.

在AWS Lambda上使用AOT编译,需要在与AWS上使用的操作系统兼容的操作系统上构建该库。在撰写本文时,这就是Amazon Linux 2

Furthermore, the Java version needs to match. AWS provides the Amazon Corretto Java 11 JVM. In order to have an environment to compile our library, we’ll install Amazon Linux 2 and Amazon Corretto in Docker.

此外,Java的版本也需要匹配。AWS提供了Amazon Corretto Java 11 JVM。为了有一个环境来编译我们的库,我们将在Docker中安装Amazon Linux 2Amazon Corretto

We won’t discuss all the details of using Docker and AWS Lambda but only outline the most important steps. For more information on how to use Docker, please refer to its official documentation here.

我们不会讨论使用Docker和AWS Lambda的所有细节,而只是概述了最重要的步骤。有关如何使用Docker的更多信息,请参考其官方文档这里

For more details about creating a Lambda function with Java, you can have a look at our article AWS Lambda With Java.

关于用Java创建Lambda函数的更多细节,您可以看看我们的文章AWS Lambda With Java

6.1. Configuration of Our Development Environment

6.1.我们开发环境的配置

First, we need to pull the Docker image for Amazon Linux 2 and install Amazon Corretto:

首先,我们需要提取Amazon Linux 2的Docker镜像,并安装Amazon Corretto

# download Amazon Linux 
docker pull amazonlinux 

# inside the Docker container, install Amazon Corretto
yum install java-11-amazon-corretto

# some additional libraries needed for jaotc
yum install binutils.x86_64

6.2. Compile the Class and Library

6.2.编译类和库

Inside our Docker container, we execute the following commands:

在我们的Docker容器内,我们执行以下命令。

# create folder aot
mkdir aot
cd aot
mkdir jaotc
cd jaotc

The name of the folder is only an example and can, of course, be any other name.

文件夹的名称只是一个例子,当然也可以是任何其他名称。

package jaotc;

public class JaotCompilation {
    public static int message(int input) {
        return input * 2;
    }
}

The next step is to compile the class and library:

下一步是编译该类和库。

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

Here, it’s important to use the same garbage collector as is used on AWS. If our library cannot be loaded on AWS Lambda, we might want to check which garbage collector is actually used with the following command:

在这里,使用与AWS上使用的相同的垃圾收集器很重要。如果我们的库不能在AWS Lambda上加载,我们可能想用下面的命令检查实际使用的是哪种垃圾收集器。

java -XX:+PrintCommandLineFlags -version

Now, we can create a zip file that contains our library and class file:

现在,我们可以创建一个包含我们的库和类文件的zip文件。

zip -r jaot.zip jaotCompilation.so jaotc/

6.3. Configure AWS Lambda

6.3.配置AWS Lambda

The last step is to log into the AWS Lamda console, upload the zip file and configure out Lambda with the following parameters:

最后一步是登录AWS Lamda控制台,上传压缩文件,用以下参数配置出Lambda。

  • Runtime: Java 11
  • Handler: jaotc.JaotCompilation::message

Furthermore, we need to create an environment variable with the name JAVA_TOOL_OPTIONS and set its value to:

此外,我们需要创建一个名为JAVA_TOOL_OPTIONS的环境变量,并将其值设置为。

-XX:+UnlockExperimentalVMOptions -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so

This variable lets us pass parameters to the JVM.

这个变量让我们向JVM传递参数。

The last step is to configure the input for our Lambda. The default is a JSON input, which cannot be passed to our function, therefore we need to set it to a String which contains an integer, e.g. “1”.

最后一步是为我们的Lambda配置输入。默认的是JSON输入,它不能传递给我们的函数,因此我们需要将它设置为一个包含整数的字符串,例如 “1”。

Finally, we can execute our Lambda function and should see in the log that our AOT compiled library was loaded:

最后,我们可以执行我们的Lambda函数,并且应该在日志中看到我们的AOT编译库被加载。

57    1     loaded    ./jaotCompilation.so  aot library

7. Conclusion

7.结语

In this article, we saw how to AOT compile Java classes and modules. As this is still an experimental feature, the AOT compiler isn’t part of all distributions. Real examples are still rare to find, and it will be up to the Java community to find the best use cases for applying AOT.

在这篇文章中,我们看到了如何用AOT来编译Java类和模块。由于这仍然是一个实验性的功能, AOT编译器并不是所有发行版的一部分。真正的例子仍然很少见,将由Java社区来寻找应用AOT的最佳用例。

All the code snippets in this article can be found in our GitHub repository.

本文中的所有代码片段都可以在我们的GitHub 仓库中找到。