トレース・エージェントを使用したネイティブ・イメージの構成

Javaリフレクション、動的プロキシ・オブジェクト、JNIまたはクラス・パス・リソースを使用するJavaアプリケーションのネイティブ実行可能ファイルをビルドするには、native-imageツールにJSON形式の構成ファイルまたはコード内の事前計算メタデータを提供する必要があります。

構成ファイルは手動で作成できますが、より便利な方法は、トレース・エージェント(これ以降、エージェントと呼びます)を使用して構成を生成することです。このガイドでは、エージェントを使用してnative-imageを構成する方法を示します。JVMでアプリケーションを実行すると、エージェントによって構成が自動的に生成されます。

コード内の事前計算されたメタデータを使用してネイティブ実行可能ファイルをビルドする方法については、ドキュメントを参照してください

このガイドのサンプル・アプリケーションでは、Javaリフレクションを使用します。native-imageツールでは、JavaリフレクションAPIを使用してアクセスされるアプリケーション要素が部分的にのみ検出されます。そのため、リフレクティブにアクセスされるクラス、メソッドおよびフィールドの詳細を提供する必要があります。

構成を指定しない例

次のアプリケーションでは、Javaリフレクションの使用方法を示しています。

  1. GraalVM JDKがインストール済であることを確認します。最も簡単に始めるには、SDKMAN!を使用します。その他のインストール・オプションについては、「ダウンロード」セクションを参照してください。

  2. 次のソース・コードを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 {
             if (args.length == 0) {
                 System.err.println("You must provide the name of a class, the name of its method and input for the method");
                 return;
             }
             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);
         }
     }
    

    このJavaアプリケーションは、コマンドライン引数を使用して、実行する操作を決定します。

  3. 例をコンパイルし、次の各コマンドを実行します。
     $JAVA_HOME/bin/javac ReflectionExample.java
    
     $JAVA_HOME/bin/java ReflectionExample StringReverser reverse "hello"
    
     $JAVA_HOME/bin/java ReflectionExample StringCapitalizer capitalize "hello"
    

    各コマンドの出力は、それぞれ"olleh"および"HELLO"になります。(クラスまたはメソッドを識別する他の文字列を指定すると、例外がスローされます。)

  4. 次のように、native-imageユーティリティを使用して、ネイティブ実行可能ファイルを作成します:
     $JAVA_HOME/bin/native-image --no-fallback ReflectionExample
    

    ノート: --no-fallbackオプションをnative-imageにすると、実行可能ファイルを作成できない場合、ユーティリティは失敗します。

  5. 次のコマンドを使用して、生成されたネイティブ実行可能ファイルを実行します:
     ./reflectionexample StringReverser reverse "hello"
    

    次のような例外が表示されます:

     Exception in thread "main" java.lang.ClassNotFoundException: StringReverser
         at java.lang.Class.forName(DynamicHub.java:1338)
         at java.lang.Class.forName(DynamicHub.java:1313)
         at ReflectionExample.main(ReflectionExample.java:25)
    

    これは、静的分析から、native-imageツールがクラスStringReverserがアプリケーションで使用されていると判断できなかったため、ネイティブ実行可能ファイルに含めなかったことを示しています。

構成を指定する例

次のステップでは、エージェントとその出力を使用して、リフレクションに依存し、構成を必要とするネイティブ実行可能ファイルを作成する方法を示します。

  1. 作業ディレクトリにMETA-INF/native-imageというディレクトリを作成します:
     mkdir -p META-INF/native-image
    
  2. 次のように、エージェントを有効にしてアプリケーションを実行します:
     $JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"
    

    このコマンドは、クラスStringReverserの名前とそのreverse()メソッドを含むreflect-config.jsonという名前のファイルを作成します。

     [
         {
         "name":"StringReverser",
         "methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
         }
     ]
    
  3. ネイティブ実行可能ファイルをビルドします:
     $JAVA_HOME/bin/native-image ReflectionExample
    

    native-imageツールは、META-INF/native-imageディレクトリ内の構成ファイルを自動的に使用します。ただし、JARファイルまたは-cpフラグを使用して、META-INF/native-imageディレクトリをクラスパスに配置することをお薦めします。(これにより、ディレクトリ構造がIDE自体によって定義されるIDEユーザーの混乱が回避されます。)

  4. 実行可能ファイルをテストします。
     ./reflectionexample StringReverser reverse "hello"
     olleh
    
     ./reflectionexample StringCapitalizer capitalize "hello"
    

    次のような例外が再度表示されます:

     Exception in thread "main" java.lang.ClassNotFoundException: StringCapitalizer
         at java.lang.Class.forName(DynamicHub.java:1338)
         at java.lang.Class.forName(DynamicHub.java:1313)
         at ReflectionExample.main(ReflectionExample.java:25)
    

    トレース・エージェントもnative-imageツールも、構成ファイルが完全であることを確認できません。エージェントは、プログラムの実行時にリフレクションを使用してアクセスされるプログラム要素を監視して記録します。この場合、native-imageツールは、クラスStringCapitalizerへの参照を含めるように構成されていません。

  5. クラスStringCapitalizerを含めるように構成を更新します。次のように、reflect-config.jsonファイルを手動で編集するか、config-merge-dirオプションを使用してトレース・エージェントを再実行して既存の構成ファイルを更新できます:
     $JAVA_HOME/bin/java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"
    

    このコマンドは、クラスStringCapitalizerとそのcapitalize()メソッドの名前を含めるようにreflect-config.jsonファイルを更新します。

     [
         {
         "name":"StringCapitalizer",
         "methods":[{"name":"capitalize","parameterTypes":["java.lang.String"] }]
         },
         {
         "name":"StringReverser",
         "methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
         }
     ]
    
  6. ネイティブ実行可能ファイルを再ビルドして実行します。
     $JAVA_HOME/bin/native-image ReflectionExample
    
     ./reflectionexample StringCapitalizer capitalize "hello"
    

    これで、アプリケーションは意図したとおりに動作するはずです。