Java 9 引入了 ,能够将 class 文件直接编译成可执行二进制文件。目前 Java 9 的 已经提供了编译工具,让我们来看看它的功能吧。
首先需要 最新的 Java 9(JDK),本文编写时,最新版本是 Build 152。下载好的 JDK 只需要解压即可使用,特别注意使用前设置好
和
- PATH
两个环境变量,避免和机器上已经安装的 JDK 混淆。笔者安装到了 $HOME/bin/jdk-9,并设置了:
- JAVA_HOME
- export PATH=~/bin/jdk-9/bin:$PATH
- export JAVA_HOME=~/bin/jdk-9
需要使用
,首先需要有个测试类,首先从 Hello World 开始:
- jaotc
- class HelloWorld {
- public static void main(String[] args) {
- System.out.println("Hello World!");
- }
- }
代码非常简单,但是在执行 jaotc 之前,还需要将其编译成 class 文件,直接使用 javac 即可:
- $ javac HelloWorld.java
执行成功之后,会生成 HelloWorld.class 文件。此时直接使用 java 命令,已经可以正常运行这个类:
- $ java HelloWorld
- Hello World!
这时,就可以基于这个 class 文件,通过
命令将其编译成二进制文件了。
- jaotc
- $ jaotc --output libHelloWorld.so HelloWorld.class
如果一切正常,会生成 libHelloWorld.so 文件。
如果出现类似
的错误,是因为
- Exception in thread "main" java.lang.UnsatisfiedLinkError: /home/babydragon/bin/jdk-9/lib/libjelfshim.so: libelf.so.1: 无法打开共享对象文件: 没有那个文件或目录
需要依赖 libelf 动态链接库来创建 (最终生成的 libHelloWorld.so 文件是一个静态链接的 elf 文件)。笔者使用的是 Gentoo 系统,需要安装 dev-libs/elfutils 包,以提供 libelf.so 这个动态连接库。安装之后可以通过
- jaotc
命令进行确认:
- ldd
- $ ldd $JAVA_HOME/lib/libjelfshim.so
- linux-vdso.so.1 (0x00007ffd001f3000)
- libelf.so.1 => /usr/lib64/libelf.so.1 (0x00007f25ea2ce000)
- libc.so.6 => /lib64/libc.so.6 (0x00007f25e9f35000)
- libz.so.1 => /lib64/libz.so.1 (0x00007f25e9d1d000)
- /lib64/ld-linux-x86-64.so.2 (0x0000562318d51000)
前面通过
命令成功生成了 libHelloWorld.so。虽然命令里面参照 JEP 295 的示例将生成的文件后缀设置成了 so,但如果使用
- jaotc
命令查看,会发现它其实是一个静态链接库:
- ldd
- $ ldd libHelloWorld.so
- statically linked
通过
命令,可以看见代码段中的函数入口:
- nm
- $ nm libHelloWorld.so
- 0000000000002420 t HelloWorld.
- ()V
- 0000000000002520 t HelloWorld.main([Ljava/lang/String;)V
最后,需要执行时需要通过参数
参数指定需要加载的经过 aot 预编译好的共享库文件:
- -XX:AOTLibrary
- java -XX:AOTLibrary=./libHelloWorld.so HelloWorld
此时执行的输出,和之前不使用 AOT 的输出完全相同。
JEP 295 中已经说明,在 Java 9 初始发布的时候,只保证 java.base 模块可以被编译成 AOT 库。
继续参照 JEP 295,创建 java.base-list.txt 文件,内容主要是排除一些编译有问题的方法,具体内容参照 。
然后执行命令:
- jaotc - J - XX: +UseCompressedOops - J - XX: +UseG1GC - J - Xmx4g--compile -
- for - tiered--info--compile - commands java.base - list.txt--output libjava.base - coop.so--module java.base
在笔者的机器上(i7-6600U + 16G 内存 + 256G NVMe SSD),排除上述方法之后,编译时间大约为 9 分多钟。
- 48878 methods compiled, 4 methods failed (497771 ms)
- Parsing compiled code (1126 ms)
- Processing metadata (15811 ms)
- Preparing stubs binary (0 ms)
- Preparing compiled binary (104 ms)
- Creating binary: libjava.base-coop.o (5611 ms)
- Creating shared library: libjava.base-coop.so (7306 ms)
- Total time: 542536 ms
完成之后,就可以使用 AOT 版本的 java.base 模块:
- java - XX: AOTLibrary = java_base / libjava.base - coop.so,
- . / libHelloWorld.so HelloWorld
同样,针对 AOT,jvm 也新增了参数打印哪些方法是通过加载 AOT 预编译库执行。
- java - XX: +PrintAOT - XX: AOTLibrary = java_base / libjava.base - coop.so,
- . / libHelloWorld.so HelloWorld
输出可以和不使用 java.base 的 AOT 进行比较,发现不使用 java.base 的 AOT 库,只能会加载 libHelloWorld.so 中对应的方法。
- $ java -XX:+PrintAOT -XX:AOTLibrary=./libHelloWorld.so HelloWorld
- 11 1 loaded ./libHelloWorld.so aot library
- 105 1 aot[ 1] HelloWorld.
- ()V
- 105 2 aot[ 1] HelloWorld.main([Ljava/lang/String;)V
- Hello World!
- $ java -XX:+PrintAOT -XX:AOTLibrary=java_base/libjava.base-coop.so,./libHelloWorld.so HelloWorld
- 13 1 loaded java_base/libjava.base-coop.so aot library
- 13 2 loaded ./libHelloWorld.so aot library
- [Found [Z in java_base/libjava.base-coop.so]
- [Found [C in java_base/libjava.base-coop.so]
- [Found [F in java_base/libjava.base-coop.so]
- [Found [D in java_base/libjava.base-coop.so]
- [Found [B in java_base/libjava.base-coop.so]
- [Found [S in java_base/libjava.base-coop.so]
- [Found [I in java_base/libjava.base-coop.so]
- [Found [J in java_base/libjava.base-coop.so]
- 31 1 aot[ 1] java.lang.Object.
- ()V
- 31 2 aot[ 1] java.lang.Object.finalize()V
- ...
输出太长,节选部分输出,我们可以看见 java 基础类及其方法都通过 AOT 的方式进行加载。
目前 AOT 的局限有:
AOT 可能带来的好处,是 JVM 加载这些已经预编译成二进制库之后,可以直接调用,而无需再将其运行时编译成二进制码。理论上,AOT 的方式,可以减少 带来的预热时间,减少 Java 应用长期给人带来的 "第一次运行慢" 感觉。
不过,本文使用的 HelloWorld 过于简单,无法通过对比得出 AOT 是否可以减少 JVM 初始化时间。笔者尝试对一个小型 springboot 应用进行 AOT 化,但是 springboot 框架本身无法在 Java 9 中运行。同时直接对 spring-core 的 jar 包执行 jaotc 也因为各种依赖问题而失败。
经过各种尝试,目前 Java 9 的 AOT 功能还处于很初步的阶段:
期待 Java 9 正式发布的时候,能够对 AOT 有更好的支持。
来源: