事前コンパイル(AoT)
1. 序章
この記事では、 JEP-295 で説明され、Java9で実験的な機能として追加されたJavaAhead of Time(AOT)コンパイラについて説明します。
まず、AOTとは何かを確認し、次に簡単な例を確認します。 第三に、AOTのいくつかの制限を確認し、最後に、いくつかの可能なユースケースについて説明します。
2. 事前コンパイルとは何ですか?
AOTコンパイルは、Javaプログラムのパフォーマンス、特にJVMの起動時間を改善する1つの方法です。 JVMはJavaバイトコードを実行し、頻繁に実行されるコードをネイティブコードにコンパイルします。 これは、ジャストインタイム(JIT)コンパイルと呼ばれます。 JVMは、実行中に収集されたプロファイリング情報に基づいて、JITでコンパイルするコードを決定します。
この手法により、JVMは高度に最適化されたコードを生成し、ピークパフォーマンスを向上させることができますが、実行されたコードはまだJITコンパイルされていないため、起動時間は最適ではない可能性があります。 AOTは、このいわゆるウォーミングアップ期間の改善を目指しています。 AOTに使用されるコンパイラはGraalです。
この記事では、JITとGraalの詳細については説明しません。 Java9および10でのパフォーマンスの向上の概要については、他の記事も参照してください。 GraalJITコンパイラの詳細として。
3. 例
この例では、非常に単純なクラスを使用してコンパイルし、結果のライブラリの使用方法を確認します。
3.1. AOTコンパイル
サンプルクラスを簡単に見てみましょう。
public class JaotCompilation {
public static void main(String[] argv) {
System.out.println(message());
}
public static String message() {
return "The JAOT compiler says 'Hello'";
}
}
AOTコンパイラを使用する前に、Javaコンパイラを使用してクラスをコンパイルする必要があります。
javac JaotCompilation.java
次に、結果の JaotCompilation.class を、標準のJavaコンパイラと同じディレクトリにあるAOTコンパイラに渡します。
jaotc --output jaotCompilation.so JaotCompilation.class
これにより、現在のディレクトリにライブラリjaotCompilation.soが生成されます。
3.2. プログラムの実行
その後、プログラムを実行できます。
java -XX:AOTLibrary=./jaotCompilation.so JaotCompilation
引数-XX:AOTLibrary は、ライブラリへの相対パスまたはフルパスを受け入れます。 または、ライブラリをJavaホームディレクトリの lib フォルダにコピーして、ライブラリの名前のみを渡すこともできます。
3.3. ライブラリが呼び出されて使用されていることの確認
-XX:+ PrintAOT をJVM引数として追加することにより、ライブラリが実際にロードされたことがわかります。
java -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so JaotCompilation
出力は次のようになります。
77 1 loaded ./jaotCompilation.so aot library
ただし、これはライブラリがロードされたことを示すだけであり、実際に使用されたことを示すものではありません。 引数-verboseを渡すことにより、ライブラリ内のメソッドが実際に呼び出されていることがわかります。
java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation
出力には次の行が含まれます。
11 1 loaded ./jaotCompilation.so aot library
116 1 aot[ 1] jaotc.JaotCompilation.<init>()V
116 2 aot[ 1] jaotc.JaotCompilation.message()Ljava/lang/String;
116 3 aot[ 1] jaotc.JaotCompilation.main([Ljava/lang/String;)V
The JAOT compiler says 'Hello'
AOTコンパイル済みライブラリには、クラスフィンガープリントが含まれています。これは、.classファイルのフィンガープリントと一致する必要があります。
クラスJaotCompilation。javaのコードを変更して、別のメッセージを返しましょう。
public static String message() {
return "The JAOT compiler says 'Good morning'";
}
変更されたクラスをAOTコンパイルせずにプログラムを実行すると、次のようになります。
java -XX:AOTLibrary=./jaotCompilation.so -verbose -XX:+PrintAOT JaotCompilation
その場合、出力には次のもののみが含まれます。
11 1 loaded ./jaotCompilation.so aot library
The JAOT compiler says 'Good morning'
クラスのバイトコードが変更されたため、ライブラリ内のメソッドが呼び出されないことがわかります。この背後にある考え方は、AOTであるかどうかに関係なく、プログラムは常に同じ結果を生成するということです。コンパイルされたライブラリがロードされているかどうか。
4. その他のAOTおよびJVM引数
4.1. JavaモジュールのAOTコンパイル
モジュールをAOTコンパイルすることも可能です。
jaotc --output javaBase.so --module java.base
結果のライブラリjavaBase.soのサイズは約320MBで、ロードに時間がかかります。 AOTコンパイルするパッケージとクラスを選択することで、サイズを縮小できます。
以下でその方法を見ていきますが、すべての詳細を深く掘り下げることはしません。
4.2. コンパイルコマンドを使用した選択的コンパイル
JavaモジュールのAOTコンパイル済みライブラリが大きくなりすぎるのを防ぐために、コンパイルコマンドを追加して、AOTコンパイル対象の範囲を制限できます。これらのコマンドは、テキストファイルに含める必要があります。たとえば、ファイルcomplileCommands.txtを使用します。
compileOnly java.lang.*
次に、それをコンパイルコマンドに追加します。
jaotc --output javaBaseLang.so --module java.base --compile-commands compileCommands.txt
結果のライブラリには、パッケージjava.langのAOTコンパイル済みクラスのみが含まれます。
実際のパフォーマンスを向上させるには、JVMのウォームアップ中に呼び出されるクラスを見つける必要があります。
これは、いくつかのJVM引数を追加することで実現できます。
java -XX:+UnlockDiagnosticVMOptions -XX:+LogTouchedMethods -XX:+PrintTouchedMethodsAtExit JaotCompilation
この記事では、この手法について詳しくは説明しません。
4.3. 単一クラスのAOTコンパイル
引数–class-nameを使用して単一のクラスをコンパイルできます。
jaotc --output javaBaseString.so --class-name java.lang.String
結果のライブラリには、クラスStringのみが含まれます。
4.4. 階層化のためにコンパイル
デフォルトでは、AOTコンパイル済みコードが常に使用され、ライブラリに含まれるクラスに対してJITコンパイルは行われません。 プロファイリング情報をライブラリに含めたい場合は、引数compile-for-tieredを追加できます:
jaotc --output jaotCompilation.so --compile-for-tiered JaotCompilation.class
ライブラリ内のプリコンパイルされたコードは、バイトコードがJITコンパイルの対象になるまで使用されます。
5. AOTコンパイルの可能なユースケース
AOTのユースケースの1つは、JITコンパイルが発生する前に実行を終了する、実行時間の短いプログラムです。
もう1つのユースケースは、JITが不可能な組み込み環境です。
この時点で、AOTコンパイル済みライブラリは同一のバイトコードを持つJavaクラスからのみロードできるため、JNIを介してロードできないことにも注意する必要があります。
6. AOTとAmazonLambda
AOTコンパイルされたコードの考えられるユースケースは、短い起動時間が重要な短命のラムダ関数です。 このセクションでは、AWSLambdaでAOTコンパイル済みJavaコードを実行する方法を見ていきます。
AWS LambdaでAOTコンパイルを使用するには、AWSで使用されているオペレーティングシステムと互換性のあるオペレーティングシステムでライブラリを構築する必要があります。執筆時点では、これは Amazon Linux 2 。
さらに、Javaのバージョンが一致している必要があります。 AWSは、 Amazon Corretto Java 11JVMを提供しています。 ライブラリをコンパイルする環境を整えるために、Dockerに Amazon Linux2とAmazonCorrettoをインストールします。
DockerとAWSLambdaの使用に関するすべての詳細については説明しませんが、最も重要な手順の概要のみを説明します。 Dockerの使用方法の詳細については、Dockerの公式ドキュメントこちらを参照してください。
Javaを使用したLambda関数の作成の詳細については、記事 AWS Lambda WithJavaを参照してください。
6.1. 開発環境の構成
まず、 Amazon Linux 2 のDockerイメージをプルし、 AmazonCorrettoをインストールする必要があります。
# download Amazon Linux
docker pull amazonlinux
# inside the Docker container, install Amazon Corretto
yum install java-11-amazon-corretto
# some additional libraries needed for jaotc
yum install binutils.x86_64
6.2. クラスとライブラリをコンパイルする
Dockerコンテナー内で、次のコマンドを実行します。
# create folder aot
mkdir aot
cd aot
mkdir jaotc
cd jaotc
フォルダの名前は単なる例であり、もちろん他の名前にすることもできます。
package jaotc;
public class JaotCompilation {
public static int message(int input) {
return input * 2;
}
}
次のステップは、クラスとライブラリをコンパイルすることです。
javac JaotCompilation.java
cd ..
jaotc -J-XX:+UseSerialGC --output jaotCompilation.so jaotc/JaotCompilation.class
ここでは、AWSで使用されているものと同じガベージコレクターを使用することが重要です。 ライブラリをAWSLambdaにロードできない場合は、次のコマンドで実際に使用されているガベージコレクターを確認することをお勧めします。
java -XX:+PrintCommandLineFlags -version
これで、ライブラリとクラスファイルを含むzipファイルを作成できます。
zip -r jaot.zip jaotCompilation.so jaotc/
6.3. AWSLambdaを設定する
最後のステップは、AWS Lamdaコンソールにログインし、zipファイルをアップロードして、次のパラメーターを使用してLambdaを設定することです。
- ランタイム: Java 11
- ハンドラー: jaotc.JaotCompilation :: message
さらに、JAVA_TOOL_OPTIONSという名前の環境変数を作成し、その値を次のように設定する必要があります。
-XX:+UnlockExperimentalVMOptions -XX:+PrintAOT -XX:AOTLibrary=./jaotCompilation.so
この変数を使用すると、JVMにパラメーターを渡すことができます。
最後のステップは、Lambdaの入力を構成することです。 デフォルトはJSON入力であり、関数に渡すことはできません。したがって、整数を含む文字列に設定する必要があります。 「1」。
最後に、Lambda関数を実行でき、AOTコンパイル済みライブラリがロードされたことをログで確認できます。
57 1 loaded ./jaotCompilation.so aot library
7. 結論
この記事では、JavaクラスとモジュールをAOTコンパイルする方法を説明しました。 これはまだ実験的な機能であるため、AOTコンパイラはすべてのディストリビューションの一部ではありません。 実際の例を見つけることはまだまれであり、AOTを適用するための最良のユースケースを見つけるのはJavaコミュニティ次第です。
この記事のすべてのコードスニペットは、GitHubリポジトリにあります。