到達可能性メタデータ

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リフレクション・メタデータはreflection-config.jsonで指定され、サンプル・エントリは次のようになります:

{
  "name": "Foo"
}

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

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

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

メタデータ・タイプ

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

リフレクション

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

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

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

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ファイルで指定できます。JSONファイルは、リフレクション・エントリの配列です:

[
    {
        "condition": {
            "typeReachable": "<condition-class>"
        },
        "name": "<class>",
        "methods": [
            {"name": "<methodName>", "parameterTypes": ["<param-one-type>"]}
        ],
        "queriedMethods": [
            {"name": "<methodName>", "parameterTypes": ["<param-one-type>"]}
        ],
        "fields": [
            {"name": "<fieldName>"}
        ],
        "allDeclaredMethods": true,
        "allDeclaredFields": true,
        "allDeclaredConstructors": true,
        "allPublicMethods": true,
        "allPublicFields": true,
        "allPublicConstructors": 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メタデータのJSONスキーマは、リフレクション・メタデータ・スキーマと同じです。

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

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

次のコードはテキスト・ファイルにアクセスするため、リソース・メタデータを提供する必要があります:

class Example {
    public void conquerTheWorld() {
        ...
        InputStream plan = Example.class.getResourceAsStream("plans/v2/conquer_the_world.txt");
        ...
    }
}

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

コードで使用されているリソースおよびリソース・バンドルを指定することはできません。

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"]
    }
  ]
}

ネイティブ・イメージは、すべてのリソースを繰り返し処理し、includesで指定されたJava正規表現に対して相対パスを照合します。パスが正規表現に一致する場合、リソースが含まれます。excludes文は、指定されたpatternに一致する特定の含まれるリソースを省略するようにnative-imageに指示します。

動的プロキシ

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ファイルで提供されます。

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

シリアライズ

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

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

シリアライズに使用されるクラスをコードに登録することはできません。

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>",
      "customTargetConstructorClass": "<custom-target-constructor-class>"
    }
  ]
}

typesの各エントリは、nameで指定されたクラスのオブジェクトのシリアライズおよびデシリアライズを可能にします。

ラムダ・シリアライズもサポートされています。nameで指定されたクラスのメソッドで宣言されたすべてのラムダをシリアライズ/デシリアライズできます。

事前定義済クラス

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

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

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

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

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

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

事前定義済クラスのメタデータは、predefined-classes-config.jsonファイルで提供されます。

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

JSONスキーマには、リストされたクラスのバイトコードを含むagent-extracted-predefined-classesディレクトリが付属しています。

その他の情報