ノート:
- このチュートリアルは、Oracle提供の無料ラボ環境で入手できます。
- Oracle Cloud Infrastructureの資格証明、テナンシおよびコンパートメントに例の値を使用します。演習を完了するときは、これらの値をクラウド環境に固有の値に置き換えます。
意見とGraalVM Native Imageについて
イントロダクション
このラボは、GraalVM Native Imageにおけるリフレクションの動作について詳しく理解したい開発者向けです。
GraalVM Native Imageを使用すると、Javaアプリケーションを自己完結型ネイティブ実行可能ファイルに事前にコンパイルできます。GraalVM Native Imageでは、実行時にアプリケーションで必要とされるコードのみがネイティブの実行可能ファイルに追加されます。
これらのネイティブの実行可能ファイルには、次のような多くの重要な利点があります。
- JVMに必要なリソースの一部を使用するため、実行速度が低い
- ミリ秒単位で開始
- すぐにピーク・パフォーマンスを提供、ウォームアップなし
- 軽量コンテナ・イメージにパッケージ化することで、より迅速かつ効率的なデプロイメントを実現できます。
- 攻撃対象領域を削減(今後のラボではさらに活用)
業界をリードするマイクロサービス・フレームワークの多くは、Micronaut、Spring、Helidon、Quarkusなど、GraalVM Native Imageのコンパイルを事前にサポートしています。
さらに、ネイティブ・イメージ用のMavenおよびGradleプラグインがあり、Javaアプリケーションのビルド、テストおよび実行をネイティブ実行可能ファイルとして容易にします。
注意: Oracle Cloud Infrastructure (OCI)は、追加コストなしでGraalVM Enterpriseを提供します。
予想実習時間: 30分
ラボの目的
この演習では、次のタスクを実行します。
native-image
ビルド・ツールを使用して、リフレクションを使用するJavaコードをスタンドアロン実行可能ファイルに構築する方法について学習します- GraalVMの補助付き構成ツールについて学ぶ
注:ラップトップアイコンが表示された場合は常に、このようにする必要があります。これらに注意しなさい。
# This is where we you will need to do something
開発環境は、リモート・ホスト(Oracle Linux 8、1 CPUおよび32GBのメモリーを備えたOCIコンピュート・インスタンス)によって提供されます。
Luna Labsデスクトップ環境は、リモート・ホストの準備が完了する前に表示され、最大2分かかる場合があります。
ステップ1:仮想ホストへの接続および開発環境の確認
リモート・ホストへの接続は、Lunaデスクトップ環境でセットアップ・スクリプトを実行します。このスクリプトは、「リソース」タブで使用できます
-
デスクトップで、「Luna-Lab.html」アイコンをダブルクリックします。ページが開き、ラボに固有のOracle Cloud Infrastructure資格証明および情報が表示されます。
-
「リソース」タブが表示されます。コンピュート・インスタンスが10分のクラウドでプロビジョニングされている間は、「リソース」タイトルの横に表示された歯車が回転することに注意してください。
-
インスタンスのプロビジョニング時に最大2分かかる場合、「リソース」タブに次が表示されます
-
「リソース」タブから、VSコード環境を設定する構成スクリプトをコピーします。「詳細の表示」リンクをクリックして、構成を表示します。次のスクリーンショットに示すように、これをコピーします。
-
次のスクリーンショットに示すように、ターミナルを開きます。
-
構成コードを端末に貼り付け、VSコードを開きます。
完了しました。これで、Oracle Cloudのリモート・ホストに正常に接続されました。
ステップ2:クローズ済ワールド仮定
GraalVMとともに提供されるnative-image
ツールでスタンドアロン実行可能ファイルを構築することは、Javaアプリケーションの構築とは少し異なります。「ネイティブ・イメージ」では、「クローズド・ワールド」仮定と呼ばれるものが使用されます。
「Closed World」という仮定は、実行時にコールできるアプリケーション内のすべてのバイトコードが、構築時に認識される(つまり、native-image
ツールがスタンドアロン実行可能ファイルを構築している場合)必要があることを意味します。
続行する前に、GraalVM Native Imageで作成されたアプリケーションの構築/実行モデルについて検討してみてください。
- Javaソース・コードのJavaバイト・コード・クラスへのコンパイル
native-image
ツールを使用して、ネイティブ実行可能ファイルにこれらのJavaバイト・コード・クラスを作成します。- ネイティブ実行可能ファイルを実行
しかし、手順2で実際に何が起こりますか。
最初に、native-image
ツールは分析を実行して、アプリケーション内のどのクラスが到達可能かを確認します。これについては、まもなく詳細に見ていきます。
次に、初期化しても安全であることがわかっているクラス(セーフ・クラスの自動初期化)が初期化されます。初期化されたクラスのクラス・データはイメージ・ヒープにロードされ、その後、スタンドアロンの実行可能ファイルに保存されます(テキスト・セクション内)。これは、このようなファスト・スタート・アプリケーションに合せて作成できるGraalVM native-image
ツールの機能の1つです。
注::これはオブジェクトの初期化とは異なります。オブジェクトの初期化は、ネイティブ実行可能ファイルの実行時に行われます。
私たちは到達可能性の話題に戻ると言った。前述のように、分析では、スタンドアロンの実行可能ファイルに含める必要があるクラス、メソッドおよびフィールドが決定されます。分析は静的であり、コードは実行されません。解析では、ダイナミック クラスのロードとリフレクションの使用(を参照)のケースを判断できますが、選択できない場合があります。
Javaの動的機能を処理するには、どのクラスが反射を使用するか、どのクラスがダイナミカル・ロードされているかについて分析が知っている必要があります。
例を見てみましょう。
ステップ3:反射を使用した例
次のクラスReflectionExample.java
があるとします(このコピーはディレクトリdemo/ReflectionExample.java
にあります)。
import java.lang.reflect.Method;
class StringReverser {
static String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
}
class StringCapitalizer {
static String capitalize(String input) {
return input.toUpperCase();
}
}
public class ReflectionExample {
public static void main(String[] args) throws ReflectiveOperationException {
String className = args[0];
String methodName = args[1];
String input = args[2];
Class<?> clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod(methodName, String.class);
Object result = method.invoke(null, input);
System.out.println(result);
}
}
最初に、VSコード内に端末を作成します。これは、「ターミナル」→「新規ターミナル」から実行します
次に、コードを作成します。シェルで、VSコード内から次のコマンドを実行します。
javac ReflectionExample.java
クラスReflectionExample
のmainメソッドは、引数として名前が渡されたクラス(非常に動的なユースケース)をロードします。クラスの2番目の引数は、動的にロードされるクラスで起動されるメソッド名です。
それを実行して、何が実行されているかを確認します。
java ReflectionExample StringReverser reverse "hello"
想定どおり、クラスStringReverser
のメソッドreverse
がリフレクションを介して見つかりました。メソッドが呼び出され、入力文字列"hello"が元に戻されました。今のところは順調だよ。
よろしいですか。ただし、ネイティブ・イメージをプログラムから構築しようとするとどうなりますか。やってみよう。シェルで次のコマンドを実行します。
native-image --no-fallback ReflectionExample
注:
native-image
の--no-fallback
オプションを指定すると、スタンドアロンのネイティブexecutabaleを構築できない場合、ビルドは失敗します。
生成されたネイティブ実行可能ファイルを実行し、その動作を確認します。
./reflectionexample StringReverser reverse "hello"
Exception in thread "main" java.lang.ClassNotFoundException: StringReverser
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.Class.forName(DynamicHub.java:1214)
at ReflectionExample.main(ReflectionExample.java:21)
ここで何が起きたのですか。ネイティブ実行可能ファイルはクラスStringReverser
を見つけることができなかったようです。どのようになったか?今までには、その理由がおそらくわかると思います。クローズされたワールド仮定。
native-image
ツールが実行した分析中に、クラスStringReverser
が使用されたことを判別できませんでした。そのため、生成されたネイティブ実行可能ファイルからクラスを削除しました。ノート:スタンドアロンの実行可能ファイルから不要なクラスを削除することで、使用されることがわかっているクラスのみを含めて構築されるコードを縮小するツール。ここまで見たように、これは反射の問題を招くかもしれませんが、幸運にもこれに対処する方法があります。
ステップ4:ネイティブ・イメージ反射構成の導入
native-image
ビルド・ツールに、特別な構成ファイルを使用したリフレクションのインスタンスについて通知できます。これらのファイルはJSON
で記述され、フラグを使用してnative-image
ツールに渡すことができます。
そのため、native-image
ビルド・ツールには、その他のタイプの構成情報を渡すことができますか。ツールは現在、詳細を含むファイルの読み取りをサポートしています。
- リフレクション
- リソース -アプリケーションで必要なリソース・ファイル
- JNI
- 動的プロキシ
- シリアル化
この実習での意見に対処する方法を検討しているだけなので、そのことに焦点を当てます。
次に、これらのファイルの例を示します(ここから説明します)。
[
{
"name" : "java.lang.Class",
"queryAllDeclaredConstructors" : true,
"queryAllPublicConstructors" : true,
"queryAllDeclaredMethods" : true,
"queryAllPublicMethods" : true,
"allDeclaredClasses" : true,
"allPublicClasses" : true
},
{
"name" : "java.lang.String",
"fields" : [
{ "name" : "value" },
{ "name" : "hash" }
],
"methods" : [
{ "name" : "<init>", "parameterTypes" : [] },
{ "name" : "<init>", "parameterTypes" : ["char[]"] },
{ "name" : "charAt" },
{ "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
]
},
{
"name" : "java.lang.String$CaseInsensitiveComparator",
"queriedMethods" : [
{ "name" : "compare" }
]
}
]
ここから、Reflection APIを介してアクセスされるクラスおよびメソッドを構成する必要があることがわかります。この操作は手動で実行できますが、これらの構成ファイルを生成する最も便利な方法は、補助された構成javaagent
を使用することです。
ステップ5:ネイティブ・イメージ、補助構成: Javaエージェントの入力
完全なリフレクション構成ファイルを最初から作成することは確実可能ですが、GraalVM Javaランタイムはjavaトレース・エージェント(javaagent
)を提供しています。このエージェントは、アプリケーションの実行時に自動的に生成されます。
これをやってみよう。
トレース・エージェントを有効にしてアプリケーションを実行します。このシェルで次を実行します。
# Note: the tracing agent parameter must come before the classpath and jar params on the command ine
java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"
作成した構成を確認します。
cat META-INF/native-image/reflect-config.json
[
{
"name":"StringReverser",
"methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
}
]
次の例に示すように、native-image-agent=config-merge-dir
を指定すると、このプロセスを複数回実行でき、実行がマージされます。
java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"
スタンドアロン実行可能ファイルを構築すると、提供された構成が使用されます。次のように構築します。
native-image --no-fallback ReflectionExample
それよりもうまく機能するかどうかを見ていきましょう。
./reflectionexample StringReverser reverse "hello"
はい。
結論
GraalVM Native Imageのスタンドアロン・実行可能ファイルを構築することは、クローズド・ワールド仮定に頼っています。これは、スタンドアロン実行可能ファイルを構築する際に、コード内で発生する可能性のある反射のケースについて事前に知っておく必要があることです。
GraalVMプラットフォームは、リフレクションが使用される場合に、native-image
ビルド・ツールに指定する方法を提供します。ノート:簡単な場合、native-image
ツールでは、これら自体を検出できます。
GraalVMプラットフォームは、Java Traceエージェントを通じて反射やその他の動的動作の使用状況を検出する方法も提供しており、native-image
ツールに必要な構成ファイルを自動的に生成できます。
トレース・エージェントを使用する際には、次のことを考慮する必要があります。
- テスト・スイートを使用します。できるだけ多くのコードを試す必要があります
- 構成ファイルのレビューと編集が必要な場合があります。
このチュートリアルを楽しんでいただければ、ネイティブ・イメージの使用時にリフレクションを処理する方法について学びます。
さらに学ぶ
- ネイティブ・イメージ・アーキテクトであるChristian Wimmer GraalVM Native Image: Javaの大規模な静的分析によるプレゼンテーションをご覧ください
- GraalVM EEネイティブ・イメージのリファレンス・ドキュメント
- ネイティブ・イメージでの反射の使用
- ネイティブ・イメージでのクラス初期化
- トレース・エージェントを使用した補助的な構成
その他の学習リソース
docs.oracle.com/learnの他のラボを調べるか、Oracle Learning YouTubeチャネルでさらに無料の学習コンテンツにアクセスします。さらに、education.oracle.com/learning-explorerにアクセスしてOracle Learning Explorerにします。
製品ドキュメントは、Oracleヘルプ・センターを参照してください。
Understanding Reflection and GraalVM Native Image
F54909-01
March 2022
Copyright © 2022, Oracle and/or its affiliates.