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