http://openjdk.java.net/jeps/295 をテキトーに訳した
JEP 295: Ahead-of-Time Compilation
Owner Vladimir Kozlov Created 2016/09/15 01:20 Updated 2016/10/27 19:50 Type Feature Status Targeted Component hotspot/compiler Scope Implementation Discussion hotspot dash compiler dash dev at openjdk dot java dot net Effort M Duration M Priority 1 Reviewed by John Rose, Mikael Vidstedt Endorsed by John Rose Release 9 Issue 8166089
Summary
仮想マシンの起動前にJavaクラスをネイティブコードにコンパイルします。
Goals
- ピーク時パフォーマンスへの影響を最小限に留めつつ、小・大規模双方のJavaアプリケーションで開始時間を改善します。
- ユーザの作業手順の変更はなるべく最小限に抑えます。
Non-Goals
コンパイル済みコードの保存およびロードを行う公開ライブラリ的なメカニズムの提供は行いません。
Motivation
JITコンパイラは高速ですが、JITが完全にウォームアップするのに長時間を要するほど大規模なJavaプログラムとなる場合があります。低頻度使用のJavaメソッドは一度もコンパイルされない可能性があり、インタープリタ実行が繰り返されるとパフォーマンス悪化の原因となる可能性が潜在的に存在します。
Description
初期リリースではサポート対象となるモジュールはjava.base
のみとします。これは問題空間を限定するためで、その理由はjava.base
のJavaコードは周知のもので、かつ、一連の内部的なテストを実行可能なためです。任意のその他のJDKやユーザーコードのAOTコンパイルは試験的なものです。
java.base
モジュールにAOTを適用するには、ユーザはモジュールをコンパイルしてJDKインストールライブラリにコピーするかJavaコマンドラインで指定します。その点以外は、AOTコンパイルされたコードの使用はエンドユーザに対して完全に透過的です。
AOTコンパイルは新規ツールのjaotc
で行います。
jaotc --output libHelloWorld.so HelloWorld.class
jaotc --output libjava.base.so --module java.base
コード生成のバックエンドにはGraalを使用します。
JVM開始およびAOT初期化中に、コードは既知の場所もしくはコマンドラインのAOTLibraryフラグの指定場所から共有ライブラリの参照を試行します。もし共有ライブラリが存在する場合、それが使われます。もし共有ライブラリが見つからない場合、AOTはそのJVMインスタンスでは無効になります。
java -XX:AOTLibrary=./libHelloWorld.so,./libjava.base.so HelloWorld
java.base
にAOTライブラリをインストールしてビルドする方法を指定する新規のAOTフラグとjaotc
フラグは以降のサブセクションで示します。
AOTコンパイル済みのコードに対して使われるコンテナフォーマットは共有ライブラリです。JDK 9では共有ライブラリフォーマットがELFのLinux/x64のみサポートします。AOTライブラリのAOTコンパイル済みコードは、既存のCodeCacheの拡張としてJVMは扱います。
Javaクラスがロードされる時、AOTライブラリ内に対応するAOTコンパイル済コードがあればJVMはそれを参照し、Javaメソッドディスクリプタからリンクを貼ります。AOTコンパイル済コードは通常のJITコンパイルコードと同様のnvocation/deoptimization/unloadingルールに従います。
ソースコードの変更やクラス変換および再定義などにより、クラスバイトコードは変更可能なため、JVMはそうした変更を検出し、もしバイトコードがミスマッチとなる場合はAOTコンパイル済コードを除去する必要があります。これはclass fingerprintingで実現します。AOTコンパイル時に各クラスごとのfingerprintを生成して共有ライブラリのデータセクションに保存します。その後、クラスロード時にそのクラスにAOTコンパイル済コードがあると、現在のバイトコード用のfingerprintが共有ライブラリ内に保存されているものと比較されます。ミスマッチの場合、対象クラスのAOTコードは使われません。
AOT usage
AOTコンパイルの実行にはjaotc
ツールを用います。ツールはjavac
同様にJavaインストールの一部です。
jaotc --output libHelloWorld.so HelloWorld.class
次に、アプリケーション実行時に生成したAOTライブラリを指定します。
java -XX:AOTLibrary=./libHelloWorld.so HelloWorld
今回のリリースではAOTコンパイル時と実行時に同一のJavaランタイム設定を使うようにして下さい。例えば、
jaotc -J-XX:+UseParallelGC -J-XX:-UseCompressedOops --output libHelloWorld.so HelloWorld.class
java -XX:+UseParallelGC -XX:-UseCompressedOops -XX:AOTLibrary=./libHelloWorld.so HelloWorld
同一のJDKビルド種類(product or debug)を使用してください*1。
ランタイム設定はAOTライブラリ内に記録されて、実行時にライブラリがロードされる際に検証されます。検証失敗時にはそのAOTライブラリは使われず、JVMの実行は継続するか、-XX:+UseAOTStrictLoading
指定時には終了します。
JVM開始中にAOT初期化コードは既知の場所か-XX:AOTLibrary
オプションが指定するライブラリ内から共有ライブラリを参照します。共有ライブラリがある場合、それが使われます。もし共有ライブラリが見つからない場合、AOTはそのJVMインスタンス実行では無効になります。
AOTライブラリは--compile-for-tiered
で制御する二つのモードでコンパイルが可能です。
- Non-tiered AOTのコンパイル済みコードは、静的コンパイルをしたC++コードのような振る舞いとなり、プロファイリング情報は収集されずJIT再コンパイルは発生しません。
- Tiered AOTのコンパイル済みコードはプロファイリング情報を収集します。このプロファイリングはC1メソッドがTier 2でコンパイルするsimple profilingのと同じです。AOTメソッドがAOT実行スレッショールドにヒットすると、そのメソッドは完全なプロファイリング情報を収集するためにまずC1 at Tier 3で再コンパイルされます。これはC2 JIT再コンパイルに必要で、最適化コードを生成してピーク時アプリケーションパフォーマンスを出すために行われます。
完全プロファイリングのオーバーヘッドは、とりわけjava.base
などのモジュールのメソッドでは、すべてのメソッドに適用するには大きすぎるため、Tier 3での再コンパイルのためには更に別のステップが必要です。ユーザーアプリケーションで、Tier 3相当のプロファイリングでAOTコンパイルを行えるようにするかもしれませんが、JDK 9ではサポートしません。
java.base
メソッドのJIT再コンパイルはピークパフォーマンスを得るのが望ましいため、java.base
の論理コンパイルモードはtiered AOTです。特定シナリオ下ではnon-tiered AOTコンパイルが適しています。これには予測可能な振る舞いを要求するアプリケーションや、ピークパフォーマンスよりもフットプリントの方が重要な場合や、動的なコード生成を許可しないシステムが該当します。こうしたケースの場合、AOTコンパイルはアプリケーション全体で必要であり、JDK 9で試験運用を行います。
AOTライブラリセットは異なる実行環境向けの生成が可能です。特定のランタイム設定用に生成されたjava.base
AOTライブラリに対するwell-knownな名前をJVMは解釈します*2。$JAVA_HOME/libディレクトリを参照して現在のランタイム設定に対応するライブラリをロードします。
-XX:-UseCompressedOops -XX:+UseG1GC : libjava.base.so -XX:+UseCompressedOops -XX:+UseG1GC : libjava.base-coop.so -XX:-UseCompressedOops -XX:+UseParallelGC : libjava.base-nong1.so -XX:+UseCompressedOops -XX:+UseParallelGC : libjava.base-coop-nong1.so
以下のJavaモジュール用のAOTライブラリ名をJVMは受け付けますが、これらのコンパイル・インストール・使用は試験運用です。
jdk.compiler jdk.scripting.nashorn jdk.vm.ci jdk.vm.compiler
Steps to generate and use an AOT library for the java.base module
java.base
モジュールのコンパイルにはjaotc
を使います。全メソッド(約50000メソッド)のコンパイル用にデータを保持するため巨大なヒープを必要とします。
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
java.base
のいくつかのメソッドはコンパイルに失敗するため--compile-comands
オプションで除外されています。
cat java.base-list.txt # jaotc: java.lang.StackOverflowError exclude sun.util.resources.LocaleNames.getContents()[[Ljava/lang/Object; exclude sun.util.resources.TimeZoneNames.getContents()[[Ljava/lang/Object; exclude sun.util.resources.cldr.LocaleNames.getContents()[[Ljava/lang/Object; exclude sun.util.resources..*.LocaleNames_.*.getContents\(\)\[\[Ljava/lang/Object; exclude sun.util.resources..*.LocaleNames_.*_.*.getContents\(\)\[\[Ljava/lang/Object; exclude sun.util.resources..*.TimeZoneNames_.*.getContents\(\)\[\[Ljava/lang/Object; exclude sun.util.resources..*.TimeZoneNames_.*_.*.getContents\(\)\[\[Ljava/lang/Object; # java.lang.Error: Trampoline must not be defined by the bootstrap classloader exclude sun.reflect.misc.Trampoline.<clinit>()V exclude sun.reflect.misc.Trampoline.invoke(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; # JVM asserts exclude com.sun.crypto.provider.AESWrapCipher.engineUnwrap([BLjava/lang/String;I)Ljava/security/Key; exclude sun.security.ssl.* exclude sun.net.RegisteredDomain.<clinit>()V
AOTライブラリを生成したら-XX:AOTLibrary
オプションでアプリケーション実行時に指定します(デフォルトではjavaはG1とJDK 9のcompressed oopsを使用するため、このフラグを指定する必要はありません)。
java -XX:AOTLibrary=./libjava.base-coop.so,./libHelloWorld.so HelloWorld
もしくは、生成したAOTライブラリをJDKインストールディレクトリにコピーします(ディレクトリのパーミッションを調整する必要があるかもしれません)。
cp libjava.base-coop.so $JAVA_HOME/lib/
上記の場合、コマンドラインで指定しなくても自動的にロードされます。
java -XX:AOTLibrary=./libHelloWorld.so HelloWorld
ライブラリサイズの縮小にはAOTライブラリから未使用シンボルの削除(strip
)を検討してください。
New run-time AOT flags
-XX:+/-UseAOT
AOTコンパイル済みファイルを使います。デフォルトはONです。
-XX:AOTLibrary=<file>
AOTライブラリファイルのリストを指定します。コロン(:)あるいは(,)でそれぞれのライブラリエントリを区切ります。
-XX:+/-PrintAOT
使われるAOT klassesとメソッドを表示します。
診断に利用可能なフラグはもう一つあります(-XX:+UnlockDiagnosticVMOptions
フラグの指定が必要)。
-XX:+/-UseAOTStrictLoading
AOTライブラリの持つランタイム設定が現在のランタイム設定とマッチしない場合JVMは終了します。
JVMランタイムはJEP 158: Unified JVM Loggingで統合される以下のUnified Logging AOTタグを持ちます。
aotclassfingerprint
クラスのfingerprintがAOTライブラリに記録されているfingerprintとマッチしない場合にログを生成します。
aotclassload
対応するクラスデータがAOTライブラリ内に見つかった場合にログを生成します。
aotclassresolve
AOTコンパイル済コードからクラス解決のためのリクエストの成功・失敗ログを生成します。
jaotc: The Java Ahead-Of-Time compiler
jaotc
はコンパイル済javaメソッドに対してネイティブコードを生成するjava静的コンパイラです。裏側のコード生成にはGraal、.soAOTライブラリに生成にはlibelfを使用します。
このツールはjava installationの一部でjavacと同様な使い方をします。
jaotc <options> <--module name>
もしくは、
jaotc <options> <list of classes or/and jar files>
以下のjaotc
フラグが使用可能です。
--module <name>
コンパイルを行うためのモジュール。
--module-path <path>
アプリケーションモジュールの参照先指定。未指定の場合JDKインストールディレクトリを参照します。
--output <file>
出力ファイル名。デフォルト名は"unnamed.so"です。
--compile-commands <file>
コンパイルコマンドにはファイル名を指定します。
exclude sun.util.resources..*.TimeZoneNames_.*.getContents\(\)\[\[Ljava/lang/Object; exclude sun.security.ssl.* compileOnly java.lang.String.*
AOTは今のところ二つのコンパイルコマンドを使用可能です。
exclude - exclude compilation of specified methods compileOnly - compile only specified methods
クラスとメソッドの指定には正規表現を使います。
--compile-for-tiered
tieredコンパイルでプロファイリングコードを生成します。デフォルトではプロファイリングコードは生成されません。
--classpath <path>
ユーザクラスファイルの参照先を指定します。
--threads <number>
コンパイルに用いるスレッド数。デフォルト値はmin(16, available_cpus)です。
--ignore-errors
クラスロード中にスローされる全ての例外を無視します。デフォルトでは、クラスロードが例外をスローする場合はコンパイルを終了します。
--exit-on-error
コンパイルエラー時に終了します。デフォルトでは、失敗したコンパイルはスキップされてそれ以外のメソッドのコンパイルは続行します。
--info
コンパイルフェーズに関する情報を表示します。
--verbose
--infoフラグ有効時にコンパイルフェーズに関するより詳しい情報を表示します。
--debug
--infoと--verboseフラグ有効時により詳しい情報を表示します。
-J<flag>
--help
jaotc
のメッセージとフラグを表示します。
AOT limitations in JDK 9
- JDK 9のAOT初期リリースは64-bit javaの動作する64-bit Linuxシステムのみサポートします。
- AOTコンパイル結果であるAOT共有ライブラリ(.so)を許可するためのlibelfがシステムにインストールされていること。
- Javaアプリケーションで使われるAOTコードでは、同一システムもしくは同一設定システムでAOTコンパイルを実行すること。
- JDK 9の初期リリースではAOTコンパイルがサポートするモジュールは
java.base
に限定されます。 - 今のところG1とParallel GCのみサポートします。
- 動的に生成されるクラスとバイトコード(ラムダ式, invoke dynamic)を使用するjavaコードのコンパイルはしないでください。
- AOTコンパイルと実行には同一のJavaランタイム設定を使用して下さい。例えば、アプリケーションがAOTコードにParallel GCを使用する場合、
jaotc
ツールでもParallel GC(-Jフラグを使用)して実行してください。
以上の制限は将来のリリースで扱う予定です。
Alternatives
プロファイルもしくはコンパイルの削減は議論され続けてきましたが、議論だけではコードをコンパイルするのにかかる時間を削減できません。別案として、低レベルIRの低速コピーを節約があり、これはさほど複雑では無さそうです(but that seems no less complex)。
Testing
AOT機能に対する新規のAOT jtregテストを開発します。
すべての既存テストがAOT有効化JDKで実行可能かテストします。既に個々のナイトリーテスト設定を実行しています*3。
それ以外の設定をAOTコンパイルjava.base
モジュールを用いてAOT有効化JDK環境ですべてのテストを実行します。
Risks and Assumptions
プリコンパイルコードの使用は最適化を損なうコードとなり、結果としてパフォーマンスを下げる可能性があります。パフォーマンステストの結果では、あるアプリケーションではAOTコンパイルコードで改善が見られる一方で、明らかに悪くなるケースもありました。AOT機能はオプトインなため、ユーザアプリケーションでのパフォーマンス悪化は回避可能です。もしユーザがアプリケーション開始時間の低下に直面、あるいは、所定のピークパフォーマンスに達しない場合、-XX:-UseAOT
フラグあるいはAOTライブラリの削除でAOTを無効化できます。
Dependences
AOTコンパイラはコード生成のバックエンドにGraalを使用するため、本プロジェクトはJEP 243: Java-Level JVM Compiler Interfaceに依存しています。
本プロジェクトはJDKのGraal coreにマージされてLinux/x64ビルドで配布予定です。
*1:It includes the requirement to use the same JDK build variant: product or debug.が原文。色々と揃えてくれ、ってことだと思われるが、良い日本語訳思い浮かばず
*2:JVM knows next well-known names for java.base AOT libraries generated for specific runtime configuration.が原文
*3:This is already done as separate nightly testing configurations.が原文。ある特定の設定については既にナイトリービルドやってます、てこと?