歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java 9 AOT初探

Java 9 AOT初探

日期:2017/3/1 9:06:28   编辑:Linux編程
Java 9引入了aot編譯方式,能夠將class文件直接編譯成可執行二進制文件。目前Java 9的early access版本已經提供了編譯工具,讓我們來看看它的功能吧。

注意:按照JEP 295描述,目前版本的AOT,僅支持64位Linux操作系統。

jaotc使用

首先需要下載最新的Java 9(JDK),本文編寫時,最新版本是Build 152。下載好的JDK只需要解壓即可使用,特別注意使用前設置好PATHJAVA_HOME兩個環境變量,避免和機器上已經安裝的JDK混淆。筆者安裝到了$HOME/bin/jdk-9,並設置了:

export PATH=~/bin/jdk-9/bin:$PATH
export JAVA_HOME=~/bin/jdk-9

需要使用jaotc,首先需要有個測試類,首先從Hello World開始:


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: 無法打開共享對象文件: 沒有那個文件或目錄的錯誤,是因為jaotc需要依賴libelf動態鏈接庫來創建elf文件(最終生成的libHelloWorld.so文件是一個靜態鏈接的elf文件)。筆者使用的是Gentoo系統,需要安裝dev-libs/elfutils包,以提供libelf.so這個動態連接庫。安裝之後可以通過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)

前面通過jaotc命令成功生成了libHelloWorld.so。雖然命令裡面參照JEP 295的示例將生成的文件後綴設置成了so,但如果使用ldd命令查看,會發現它其實是一個靜態鏈接庫:

$ ldd libHelloWorld.so 
        statically linked

通過nm命令,可以看見代碼段中的函數入口:

$ nm libHelloWorld.so
0000000000002420 t HelloWorld.()V
0000000000002520 t HelloWorld.main([Ljava/lang/String;)V

最後,需要執行時需要通過參數-XX:AOTLibrary參數指定需要加載的經過aot預編譯好的共享庫文件:

java -XX:AOTLibrary=./libHelloWorld.so HelloWorld

注意:雖然已經將整個HelloWorld類都通過jaotc編譯成共享庫文件,運行時仍然需要依賴原有的HelloWorld.class文件。

此時執行的輸出,和之前不使用AOT的輸出完全相同。

來把大的——將java.base模塊編譯成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的局限有:

  • 僅支持64位Linux操作系統:這個問題不是很大,畢竟大部分線上服務器都能夠滿足;
  • 操作系統需要預裝libelf庫,以確保能夠生成elf文件:這個問題也不大,僅生成時需要;
  • AOT編譯和執行環境需要相同:畢竟是二進制文件,引入了平台相關性;
  • Java 9最初發布時,只支持java.base模塊可以編譯成AOT庫;
  • 目前只支持G1和Parallel GC兩種GC方式:前面沒有提到,AOT編譯時的JVM參數和運行時需要相同,也包括GC方式,也就是說如果用了AOT,JVM實際運行時也只能使用這兩種GC方式之一;
  • 可能會無法編譯通過動態生成class文件或者修改字節碼的java代碼(如lambda表達式、反射調用等):這個可能會比較坑,後面會講到;
  • JVM運行時參數設置必須和AOT庫編譯時相同;

AOT可能帶來的好處,是JVM加載這些已經預編譯成二進制庫之後,可以直接調用,而無需再將其運行時編譯成二進制碼。理論上,AOT的方式,可以減少JIT帶來的預熱時間,減少Java應用長期給人帶來的“第一次運行慢”感覺。

不過,本文使用的HelloWorld過於簡單,無法通過對比得出AOT是否可以減少JVM初始化時間。筆者嘗試對一個小型springboot應用進行AOT化,但是springboot框架本身無法在Java 9中運行。同時直接對spring-core的jar包執行jaotc也因為各種依賴問題而失敗。

經過各種嘗試,目前Java 9的AOT功能還處於很初步的階段:

  • 缺少maven等管理工具集成,無法方便的對項目指定jar或者class文件比構建AOT庫;
  • 大型框架還沒有官方支持,構建AOT庫難度比較高;
  • 大型框架如果直接提供AOT庫,可能會因為由特定平台構建,而在本地無法使用;

期待Java 9正式發布的時候,能夠對AOT有更好的支持。

Copyright © Linux教程網 All Rights Reserved