到達可能性メタデータ

JVMの動的言語機能(リフレクションおよびリソース処理を含む)によって、実行時に呼び出されたメソッドやリソースURLなどの動的にアクセスされるプログラム要素が計算されます。native-imageツールは、ネイティブ・バイナリをビルドしながら静的分析を実行してそれらの動的機能を判断しますが、すべての使用を常に完全に予測できるわけではありません。これらの要素をネイティブ・バイナリに確実に含めるには、native-imageビルダーに到達可能性メタデータ(メタデータと呼ばれる追加のテキスト)を提供する必要があります。ビルダーに到達可能性メタデータを提供することで、実行時にサードパーティ・ライブラリとのシームレスな互換性も保証されます。

メタデータは、次の方法でnative-imageビルダーに提供できます:

目次

コードでのメタデータの計算

コードでのメタデータの計算は、次の2つの方法で実現できます:

  1. JVMの要素に動的にアクセスする関数に定数引数を指定します。このような関数の適切な例は、Class.forNameメソッドです。次のコードを見てみます:

     class ReflectiveAccess {
         public Class<Foo> fetchFoo() throws ClassNotFoundException {
             return Class.forName("Foo");
         }
     }
    

    ネイティブ・バイナリがビルドされ、その初期ヒープに格納されると、Class.forName("Foo")は定数として計算されます。クラスFooが存在しない場合、コールはthrow ClassNotFoundException("Foo")に変換されます。

  2. ビルド時にクラスを初期化し、動的にアクセスされる要素をネイティブ実行可能ファイルの初期ヒープに格納します。たとえば:

     class InitializedAtBuildTime {
         private static Class<?> aClass;
         static {
             try {
                 aClass = Class.forName(readFile("class.txt"));
             } catch (ClassNotFoundException e) {
                 throw RuntimeException(e);
             }
         }
    
         public Class<?> fetchClass() {
             return aClass;
         }
     }
    

メタデータがコードで計算される場合、動的にアクセスされる要素は、ヒープのその部分が包含メソッド(ReflectiveAccess#fetchFooなど)または静的フィールド(InitializedAtBuildTime.aClassなど)を介してアクセス可能な場合にのみ、ネイティブ実行可能ファイルのヒープに含まれます。

JSONを使用したメタデータの指定

メタデータを必要とする各動的Java機能には、<feature>-config.JSONという名前の対応するJSONファイルがあります。JSONファイルは、ネイティブ・イメージに含める要素を通知するエントリで構成されます。たとえば、Javaリフレクション・メタデータはreflect-config.jsonで指定され、サンプル・エントリは次のようになります:

{
  "name": "Foo"
}

jsonベースのメタデータ内の各エントリは、ネイティブ・バイナリのサイズが不必要に大きくならないように条件付きである必要があります。条件は次の方法で指定します:

{
  "condition": {
    "typeReachable": "<fully-qualified-class-name>"
  },
  <metadata-entry>
}

typeReachable条件を持つエントリは、完全修飾クラスにアクセスできる場合にのみ考慮されます。現在、条件としてtypeReachableのみがサポートされています。

構成ファイルのその他の例は、GraalVM到達可能性メタデータ・リポジトリを参照してください。

メタデータ・タイプ

ネイティブ・イメージは、次のタイプの到達可能性メタデータを受け入れます:

リフレクション

コードでのリフレクション・メタデータの計算

一部のリフレクション・メソッドは特別に処理され、定数引数を指定するとビルド時に評価されます。リストされているクラスごとに、次のメソッドがあります:

次に、対応するメタデータ要素に置き換えられるコールの例を示します:

Class.forName("java.lang.Integer")
Class.forName("java.lang.Integer", true, ClassLoader.getSystemClassLoader())
Class.forName("java.lang.Integer").getMethod("equals", Object.class)
Integer.class.getDeclaredMethod("bitCount", int.class)
Integer.class.getConstructor(String.class)
Integer.class.getDeclaredConstructor(int.class)
Integer.class.getField("MAX_VALUE")
Integer.class.getDeclaredField("value")

定数配列を渡す場合、配列を宣言および移入する次のアプローチは、native-imageビルダーの観点からは同等です:

Class<?>[] params0 = new Class<?>[]{String.class, int.class};
Integer.class.getMethod("parseInt", params0);
Class<?>[] params1 = new Class<?>[2];
params1[0] = Class.forName("java.lang.String");
params1[1] = int.class;
Integer.class.getMethod("parseInt", params1);
Class<?>[] params2 = {String.class, int.class};
Integer.class.getMethod("parseInt", params2);

JSONでのリフレクション・メタデータの指定

リフレクション・メタデータは、reflect-config.jsonファイルで指定し、reflect-config-schema-v1.0.0.jsonで定義されているJSONスキーマに準拠する必要があります。スキーマには、この構成がどのように機能するかの詳細および説明も含まれます。reflect-config.jsonの例を次に示します。

[
    {
        "condition": {
            "typeReachable": "<condition-class>"
        },
        "name": "<class>",
        "methods": [
            {"name": "<methodName>", "parameterTypes": ["<param-one-type>"]}
        ],
        "queriedMethods": [
            {"name": "<methodName>", "parameterTypes": ["<param-one-type>"]}
        ],
        "fields": [
            {"name": "<fieldName>"}
        ],
        "allDeclaredClasses": true,
        "allDeclaredMethods": true,
        "allDeclaredFields": true, 
        "allDeclaredConstructors": true,
        "allPublicClasses": true,
        "allPublicMethods": true,
        "allPublicFields": true,
        "allPublicConstructors": true,
        "allRecordComponents": true,
        "allNestMembers": true,
        "allSigners": true,
        "allPermittedSubclasses": true,
        "queryAllDeclaredMethods": true,
        "queryAllDeclaredConstructors": true,
        "queryAllPublicMethods": true,
        "queryAllPublicConstructors": true,
        "unsafeAllocated": true
    }
]

Java Native Interface

Java Native Interface (JNI)を使用すると、ネイティブ・コードは任意のJavaタイプおよびタイプ・メンバーにアクセスできます。ネイティブ・イメージでは、このようなネイティブ・コードのルックアップ、書込みまたは呼出しを予測できません。JNIを使用するJavaアプリケーションのネイティブ・バイナリをビルドするには、JNIメタデータが必要になる可能性があります。たとえば、指定されたCコードの場合:

jclass clazz = FindClass(env, "java/lang/String");

java.lang.Stringクラスをルックアップします。このクラスを使用すると、たとえば、異なるStringメソッドを呼び出すことができます。前述のコールに対して生成されたメタデータ・エントリは次のようになります:

{
  "name": "java.lang.String"
}

コードでのJNIメタデータ

コードにJNIメタデータを指定することはできません。

JSONでのJNIメタデータ

JNIメタデータは、jni-config.jsonファイルで指定し、jni-config-schema-v1.0.0.jsonで定義されたJSONスキーマに準拠する必要があります。スキーマには、この構成がどのように機能するかの詳細および説明も含まれます。jni-config.jsonの例は、前述のreflect-config.jsonの例と同じです。

リソースおよびリソース・バンドル

Javaは、アプリケーション・クラスパスの任意のリソース、またはリクエスト元のコードがアクセス権限を持つモジュール・パスにアクセスできます。リソース・メタデータは、指定されたリソースおよびリソース・バンドルを生成されたバイナリに含めるようにnative-imageビルダーに指示します。このアプローチの結果、構成にリソースを使用するアプリケーションの一部(ロギングなど)は、ビルド時に効果的に構成されます。

コードでのリソース・メタデータ

ネイティブ・イメージは、次のようなjava.lang.Class#getResourceおよびjava.lang.Class#getResourceAsStreamへのコールを検出します:

次のコードは、何も設定しなくてもそのまま機能します。理由は次のとおりです:

JSONでのリソース・メタデータ

リソース・メタデータは、resource-config.jsonファイルで指定し、resource-config-schema-v1.0.0.jsonで定義されたJSONスキーマに準拠する必要があります。スキーマには、この構成がどのように機能するかの詳細および説明も含まれます。resource-config.jsonの例を次に示します。

{
  "resources": {
    "includes": [
      {
        "condition": {
          "typeReachable": "<condition-class>"
        },
        "pattern": ".*\\.txt"
      }
    ],
    "excludes": [
      {
        "condition": {
          "typeReachable": "<condition-class>"
        },
        "pattern": ".*\\.txt"
      }
    ]
  },
  "bundles": [
    {
      "condition": {
        "typeReachable": "<condition-class>"
      },
      "name": "fully.qualified.bundle.name",
      "locales": ["en", "de", "sk"]
    },
    {
      "condition": {
        "typeReachable": "<condition-class>"
      },
      "name": "fully.qualified.bundle.name",
      "classNames": [
        "fully.qualified.bundle.name_en",
        "fully.qualified.bundle.name_de"
      ]
    }
  ]
}

動的プロキシ

JDKでは、特定のインタフェース・リストに対するプロキシ・クラスの生成がサポートされています。ネイティブ・イメージでは、実行時に新しいクラスの生成がサポートされないため、これらのプロキシを使用するコードを適切に実行するにはメタデータが必要です。

ノート: プロキシの作成に使用されるインタフェース・リスト内のインタフェースの順序は重要です。インタフェースの順序が異なる2つの同一のインタフェース・リストを使用してプロキシを作成すると、2つの異なるプロキシ・クラスが作成されます。

コード例

次のコードは、2つの異なるプロキシを作成します:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

interface IA {
}

interface IB {
}

class Example {
    public void doWork() {
        InvocationHandler handler;
        ...
        Object proxyOne = Proxy.newProxyInstance(Example.class.getClassLoader(), new Class[]{IA.class, IB.class}, handler);
        Object proxyTwo = Proxy.newProxyInstance(Example.class.getClassLoader(), new Class[]{IB.class, IA.class}, handler);
        ...
    }
}

コードでの動的プロキシ・メタデータ

次のメソッドは、定数引数を使用してコールされると、ビルド時に評価されます:

JSONでの動的プロキシ・メタデータ

動的プロキシ・メタデータは、proxy-config.jsonファイルで指定し、proxy-config-schema-v1.0.0.jsonで定義されたJSONスキーマに準拠する必要があります。スキーマには、この構成がどのように機能するかの詳細および説明も含まれます。次に、proxy-config.jsonの例を示します。

[
  {
    "condition": {
      "typeReachable": "<condition-class>"
    },
    "interfaces": [
      "IA",
      "IB"
    ]
  }
]

シリアライズ

Javaでは、Serializableインタフェースを実装する任意のクラスをシリアライズできます。ネイティブ・イメージは、適切なシリアライズ・メタデータ登録によるシリアライズをサポートします。これは、シリアライズでは通常、シリアライズされるオブジェクトのクラスにリフレクティブにアクセスする必要があるため必要です。

コードでのシリアライズ・メタデータの登録

ネイティブ・イメージはObjectInputFilter.Config#createFilter(String pattern)へのコールを検出し、pattern引数が定数である場合、パターンで指定されたクラスがシリアライズ用に登録されます。たとえば、次のパターンは、シリアライズ用にクラスpkg.SerializableClassを登録します:

  var filter = ObjectInputFilter.Config.createFilter("pkg.SerializableClass;!*;")
  objectInputStream.setObjectInputFilter(proof);

このパターンを使用すると、objectInputStreamで受け取ることができるのはpkg.SerializableClassのみになるため、JVMのセキュリティが向上するというプラスの効果があります。

ワイルドカード・パターンは、エンクロージング・クラスのラムダ・プロキシ・クラスに対してのみシリアライズ登録を行います。たとえば、エンクロージング・クラスpkg.LambdaHolderでラムダ・シリアライズを登録するには、次を使用します:

  ObjectInputFilter.Config.createFilter("pkg.LambdaHolder$$Lambda*;")

"pkg.**""pkg.Prefix*"などのパターンは、あまりにも一般的で、イメージ・サイズが大きくなるため、シリアライズ登録は実行されません。

sun.reflect.ReflectionFactory#newConstructorForSerialization(java.lang.Class)およびsun.reflect.ReflectionFactory#newConstructorForSerialization(java.lang.Class)のコールの場合、ネイティブ・イメージは、すべての引数とレシーバが定数の場合にこれらの関数のコールを検出します。たとえば、次のコールはシリアライズ用にSerializlableClassを登録します:

  ReflectionFactory.getReflectionFactory().newConstructorForSerialization(SerializableClass.class);

シリアライズ用のカスタム・コンストラクタを作成するには、次を使用します:

  var constructor = SuperSuperClass.class.getDeclaredConstructor();
  var newConstructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(BaseClass.class, constructor);

プロキシ・クラスは、JSONファイルを介してのみシリアライズに登録できます。

JSONでのシリアライズ・メタデータ

シリアライズ・メタデータは、serialization-config.jsonファイルで指定し、serialization-config-schema-v1.0.0.jsonで定義されたJSONスキーマに準拠する必要があります。スキーマには、この構成がどのように機能するかの詳細および説明も含まれます。次に、serialization-config.jsonの例を示します。

{
  "types": [
    {
      "condition": {
        "typeReachable": "<condition-class>"
      },
      "name": "<fully-qualified-class-name>",
      "customTargetConstructorClass": "<custom-target-constructor-class>"
    }
  ],
  "lambdaCapturingTypes": [
    {
      "condition": {
        "typeReachable": "<condition-class>"
      },
      "name": "<fully-qualified-class-name>"
    }
  ],
 "proxies": [
   {
     "condition": {
       "typeReachable": "<condition-class>"
     },
     "interfaces": ["<fully-qualified-interface-name-1>", "<fully-qualified-interface-name-n>"]
   }
 ]
}

事前定義済クラス

ネイティブ・イメージでは、ビルド時にすべてのクラスが認識される必要があります(閉世界仮説)。

ただし、Javaでは、実行時の新しいクラスのロードをサポートしています。クラス・ロードをエミュレートするために、エージェントは、動的にロードされたクラスをトレースし、そのバイトコードを保存して、後でnative-imageビルダーで使用できるようにします。実行時に、トレース中に検出されたクラスの1つと同じ名前およびバイトコードを持つクラスをロードしようとすると、事前定義済のクラスがアプリケーションに提供されます。

ノート: 事前定義済クラスのメタデータは、手動で記述することを意図していません。

コードでの事前定義済クラス・メタデータ

コードに事前定義済クラスを指定することはできません。

JSONでの事前定義済クラス・メタデータ

事前定義済のクラス・メタデータは、predefined-classes-config.jsonファイルで指定し、predefined-classes-config-schema-v1.0.0.jsonで定義されているJSONスキーマに準拠する必要があります。スキーマには、この構成がどのように機能するかの詳細および説明も含まれます。次に、predefined-classes-config.jsonの例を示します。

[
  {
    "type": "agent-extracted",
    "classes": [
      {
        "hash": "<class-bytecodes-hash>",
        "nameInfo": "<class-name"
      }
    ]
  }
]

その他の情報