到達可能性メタデータ
JVMの動的言語機能(リフレクションおよびリソース処理を含む)によって、実行時に呼び出されたメソッドやリソースURLなどの動的にアクセスされるプログラム要素が計算されます。native-image
ツールは、ネイティブ・バイナリをビルドしながら静的分析を実行してそれらの動的機能を判断しますが、すべての使用を常に完全に予測できるわけではありません。これらの要素をネイティブ・バイナリに確実に含めるには、native-image
ビルダーに到達可能性メタデータ(メタデータと呼ばれる追加のテキスト)を提供する必要があります。ビルダーに到達可能性メタデータを提供することで、実行時にサードパーティ・ライブラリとのシームレスな互換性も保証されます。
メタデータは、次の方法でnative-image
ビルダーに提供できます:
- ネイティブ・バイナリのビルド時にコード内のメタデータを計算し、必要な要素をネイティブ・バイナリの初期ヒープに格納する。
META-INF/native-image/<group.id>/<artifact.id>
プロジェクト・ディレクトリに格納されているJSONファイルを指定する。アプリケーションのメタデータを自動的に収集する方法の詳細は、メタデータの自動収集に関する項を参照してください。
目次
- コードでのメタデータの計算
- JSONを使用したメタデータの指定
- メタデータ・タイプ
- リフレクション
- Java Native Interface
- リソースおよびリソース・バンドル
- 動的プロキシ
- シリアライズ
- 事前定義済のクラス
コードでのメタデータの計算
コードでのメタデータの計算は、次の2つの方法で実現できます:
-
JVMの要素に動的にアクセスする関数に定数引数を指定します。このような関数の適切な例は、
Class.forName
メソッドです。次のコードを見てみます:class ReflectiveAccess { public Class<Foo> fetchFoo() throws ClassNotFoundException { return Class.forName("Foo"); } }
ネイティブ・バイナリがビルドされ、その初期ヒープに格納されると、
Class.forName("Foo")
は定数として計算されます。クラスFoo
が存在しない場合、コールはthrow ClassNotFoundException("Foo")
に変換されます。 -
ビルド時にクラスを初期化し、動的にアクセスされる要素をネイティブ実行可能ファイルの初期ヒープに格納します。たとえば:
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
のみがサポートされています。
メタデータ・タイプ
ネイティブ・イメージは、次のタイプの到達可能性メタデータを受け入れます:
- Javaリフレクション(
java.lang.reflect.*
API)を使用すると、Javaコードでは、実行時に独自のクラス、メソッド、フィールドおよびそれらのプロパティを調べることができます。 - JNIを使用すると、ネイティブ・コードは実行時にクラス、メソッド、フィールドおよびそれらのプロパティにアクセスできます。
- リソースおよびリソース・バンドルでは、アプリケーションに存在する任意のファイルをロードできます。
- 動的JDKプロキシは、指定されたインタフェースのリストを実装するクラスをオンデマンドで作成します。
- シリアライズでは、ストリームとの間でJavaオブジェクトの書込みおよび読取りが可能です。
- 事前定義済クラスは、動的に生成されたクラスをサポートします。
リフレクション
コードでのリフレクション・メタデータの計算
一部のリフレクション・メソッドは特別に処理され、定数引数を指定するとビルド時に評価されます。リストされているクラスごとに、次のメソッドがあります:
java.lang.Class
:getField
、getMethod
、getConstructor
、getDeclaredField
、getDeclaredMethod
、getDeclaredConstructor
、forName
、getClassLoader
java.lang.invoke.MethodHandles
:publicLookup
、privateLookupIn
、arrayConstructor
、arrayLength
、arrayElementGetter
、arrayElementSetter
、arrayElementVarHandle
、byteArrayViewVarHandle
、byteBufferViewVarHandle
、lookup
java.lang.invoke.MethodHandles.Lookup
:in
、findStatic
、findVirtual
、findConstructor
、findClass
、accessClass
、findSpecial
、findGetter
、findSetter
、findVarHandle
、findStaticGetter
、findStaticSetter
、findStaticVarHandle
、unreflect
、unreflectSpecial
、unreflectConstructor
、unreflectGetter
、unreflectSetter
、unreflectVarHandle
java.lang.invoke.MethodType
:methodType
、genericMethodType
、changeParameterType
、insertParameterTypes
、appendParameterTypes
、replaceParameterTypes
、dropParameterTypes
、changeReturnType
、erase
、generic
、wrap
、unwrap
、parameterType
、parameterCount
、returnType
、lastParameterType
次に、対応するメタデータ要素に置き換えられるコールの例を示します:
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
}
]
フレクション・エントリ内のフィールドには、次の意味があります:
condition
: 条件付きメタデータ・エントリに関する項を参照してくださいname
: リフレクティブにルックアップされるクラスの名前。このプロパティは必須です。methods
: リフレクティブにルックアップおよび実行できるクラス・メソッドをリストします。各メソッドは、名前とパラメータ・タイプのリストによって記述されます。パラメータ・タイプは、完全修飾Javaクラス名です。queriedMethods
: ルックアップのみ可能なクラス・メソッドのリスト。各メソッドの説明は、methods
リストと同じです。fields
: 検索、読取りまたは変更できるクラス・フィールドのリスト。all<access>(Methods/Fields/Constructors)
: すべてのメソッド/フィールド/コンストラクタをrルックアップ用に登録します。メソッドおよびコンストラクタも呼び出すことができます。<access>
は、Javaでこれらのメンバーを問い合せる様々な方法を指し、Declared
またはPublic
のいずれかを指定できます。詳細は、java.lang.Class.getDeclaredMethods()
およびjava.lang.Class.getPublicMethods()
を参照してください。queryAll<access>(Methods/Constructors)
: すべてのメソッド/コンストラクタをルックアップ専用に登録します。unsafeAllocated
: このクラスのオブジェクトをUnsafe.allocateInstance
を使用して割り当てることができます。
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);
...
}
}
コードでの動的プロキシ・メタデータ
次のメソッドは、定数引数を使用してコールされると、ビルド時に評価されます:
java.lang.reflect.Proxy.getProxyClass
java.lang.reflect.Proxy.newProxyInstance
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
ディレクトリが付属しています。