Java 9 AOT初探

lese9013 7年前
   <p>Java 9引入了 aot编译方式 ,能够将class文件直接编译成可执行二进制文件。目前Java 9的 early access版本 已经提供了编译工具,让我们来看看它的功能吧。</p>    <p>注意:按照JEP 295描述,目前版本的AOT,仅支持64位Linux操作系统。</p>    <p><strong>jaotc使用</strong></p>    <p>首先需要 下载 最新的Java 9(JDK),本文编写时,最新版本是Build 152。下载好的JDK只需要解压即可使用,特别注意使用前设置好 PATH 和 JAVA_HOME 两个环境变量,避免和机器上已经安装的JDK混淆。笔者安装到了$HOME/bin/jdk-9,并设置了:</p>    <pre>  <code class="language-java">export PATH=~/bin/jdk-9/bin:$PATH  export JAVA_HOME=~/bin/jdk-9</code></pre>    <p>需要使用 jaotc ,首先需要有个测试类,首先从Hello World开始:</p>    <pre>  <code class="language-java">class HelloWorld {          public static void main(String[] args) {                  System.out.println("Hello World!");          }  }</code></pre>    <p>代码非常简单,但是在执行jaotc之前,还需要将其编译成class文件,直接使用javac即可:</p>    <pre>  <code class="language-java">$ javac HelloWorld.java</code></pre>    <p>执行成功之后,会生成HelloWorld.class文件。此时直接使用java命令,已经可以正常运行这个类:</p>    <pre>  <code class="language-java">$ java HelloWorld   Hello World!</code></pre>    <p>这时,就可以基于这个class文件,通过 jaotc 命令将其编译成二进制文件了。</p>    <pre>  <code class="language-java">$ jaotc --output libHelloWorld.so HelloWorld.class</code></pre>    <p>如果一切正常,会生成libHelloWorld.so文件。</p>    <p>如果出现类似 Exception in thread "main" java.lang.UnsatisfiedLinkError: /home/babydragon/bin/jdk-9/lib/libjelfshim.so: libelf.so.1: 无法打开共享对象文件: 没有那个文件或目录 的错误,是因为 jaotc 需要依赖libelf动态链接库来创建 <a href="/misc/goto?guid=4959734870152464051" rel="nofollow,noindex">elf文件</a> (最终生成的libHelloWorld.so文件是一个静态链接的elf文件)。笔者使用的是Gentoo系统,需要安装dev-libs/elfutils包,以提供libelf.so这个动态连接库。安装之后可以通过 ldd 命令进行确认:</p>    <pre>  <code class="language-java">$ 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)</code></pre>    <p>前面通过 jaotc 命令成功生成了libHelloWorld.so。虽然命令里面参照JEP 295的示例将生成的文件后缀设置成了so,但如果使用 ldd 命令查看,会发现它其实是一个静态链接库:</p>    <pre>  <code class="language-java">$ ldd libHelloWorld.so           statically linked</code></pre>    <p>通过 nm 命令,可以看见代码段中的函数入口:</p>    <pre>  <code class="language-java">$ nm libHelloWorld.so  0000000000002420 t HelloWorld.              ()V  0000000000002520 t HelloWorld.main([Ljava/lang/String;)V      </code></pre>    <p>最后,需要执行时需要通过参数 -XX:AOTLibrary 参数指定需要加载的经过aot预编译好的共享库文件:</p>    <pre>  <code class="language-java">java -XX:AOTLibrary=./libHelloWorld.so HelloWorld</code></pre>    <p>注意:虽然已经将整个HelloWorld类都通过jaotc编译成共享库文件,运行时仍然需要依赖原有的HelloWorld.class文件。</p>    <p>此时执行的输出,和之前不使用AOT的输出完全相同。</p>    <p>来把大的——将java.base模块编译成AOT库</p>    <p>JEP 295中已经说明,在Java 9初始发布的时候,只保证java.base模块可以被编译成AOT库。</p>    <p>继续参照JEP 295,创建java.base-list.txt文件,内容主要是排除一些编译有问题的方法,具体内容参照 <a href="/misc/goto?guid=4959734870241361080" rel="nofollow,noindex">原文</a> 。</p>    <p>然后执行命令:</p>    <pre>  <code class="language-java">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</code></pre>    <p>在笔者的机器上(i7-6600U + 16G内存 + 256G NVMe SSD),排除上述方法之后,编译时间大约为9分多钟。</p>    <pre>  <code class="language-java">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</code></pre>    <p>完成之后,就可以使用AOT版本的java.base模块:</p>    <pre>  <code class="language-java">java -XX:AOTLibrary=java_base/libjava.base-coop.so,./libHelloWorld.so HelloWorld</code></pre>    <p>同样,针对AOT,jvm也新增了参数打印哪些方法是通过加载AOT预编译库执行。</p>    <pre>  <code class="language-java">java -XX:+PrintAOT -XX:AOTLibrary=java_base/libjava.base-coop.so,./libHelloWorld.so HelloWorld</code></pre>    <p>输出可以和不使用java.base的AOT进行比较,发现不使用java.base的AOT库,只能会加载libHelloWorld.so中对应的方法。</p>    <pre>  <code class="language-java">$ 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!        </code></pre>    <pre>  <code class="language-java">$ 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  ...        </code></pre>    <p>输出太长,节选部分输出,我们可以看见java基础类及其方法都通过AOT的方式进行加载。</p>    <p><strong>实用吗?</strong></p>    <p>目前AOT的局限有:</p>    <ul>     <li>仅支持64位Linux操作系统:这个问题不是很大,毕竟大部分线上服务器都能够满足;</li>     <li>操作系统需要预装libelf库,以确保能够生成elf文件:这个问题也不大,仅生成时需要;</li>     <li>AOT编译和执行环境需要相同:毕竟是二进制文件,引入了平台相关性;</li>     <li>Java 9最初发布时,只支持java.base模块可以编译成AOT库;</li>     <li>目前只支持G1和Parallel GC两种GC方式:前面没有提到,AOT编译时的JVM参数和运行时需要相同,也包括GC方式,也就是说如果用了AOT,JVM实际运行时也只能使用这两种GC方式之一;</li>     <li>可能会无法编译通过动态生成class文件或者修改字节码的java代码(如lambda表达式、反射调用等):这个可能会比较坑,后面会讲到;</li>     <li>JVM运行时参数设置必须和AOT库编译时相同;</li>    </ul>    <p>AOT可能带来的好处,是JVM加载这些已经预编译成二进制库之后,可以直接调用,而无需再将其运行时编译成二进制码。理论上,AOT的方式,可以减少 <a href="/misc/goto?guid=4958877903590246865" rel="nofollow,noindex">JIT</a> 带来的预热时间,减少Java应用长期给人带来的“第一次运行慢”感觉。</p>    <p>不过,本文使用的HelloWorld过于简单,无法通过对比得出AOT是否可以减少JVM初始化时间。笔者尝试对一个小型springboot应用进行AOT化,但是springboot框架本身无法在Java 9中运行。同时直接对spring-core的jar包执行jaotc也因为各种依赖问题而失败。</p>    <p>经过各种尝试,目前Java 9的AOT功能还处于很初步的阶段:</p>    <ul>     <li>缺少maven等管理工具集成,无法方便的对项目指定jar或者class文件比构建AOT库;</li>     <li>大型框架还没有官方支持,构建AOT库难度比较高;</li>     <li>大型框架如果直接提供AOT库,可能会因为由特定平台构建,而在本地无法使用;</li>    </ul>    <p>期待Java 9正式发布的时候,能够对AOT有更好的支持。</p>    <p> </p>    <p>来自:http://www.infoq.com/cn/news/2017/01/Java-9-AOT-first</p>    <p> </p>