到達可能性メタデータ
JVMの動的言語機能(リフレクションやリソース処理など)によって、実行時にフィールド、メソッド、リソースURLなどの動的にアクセスされるプログラム要素が計算されます。HotSpotでは、すべてのクラス・ファイルおよびリソースが実行時に使用可能であり、ライタイムによってロードできるため、これが可能です。すべてのクラスおよびリソースの可用性と、実行時におけるそれらのロードには、メモリーと起動時間の追加のオーバーヘッドが伴います。
ネイティブ・バイナリを小さくするために、native-image
ビルダーはビルド時に静的分析を実行して、アプリケーションの正確性に必要なプログラム要素のみを判別します。小さいバイナリでは、アプリケーションの起動が速くなり、メモリー・フットプリントが少なくなりますが、コストがかかります。静的分析を介して動的にアクセスされるアプリケーション要素を判別することは、それらの要素の到達可能性が実行時にのみ使用可能なデータに依存するため、実行不可能です。
動的にアクセスされる必要な要素をネイティブ・バイナリに確実に含めるには、native-image
ビルダーに到達可能性メタデータ(これ以降、メタデータと呼びます)が必要です。ビルダーに正確で包括的な到達可能性メタデータを提供することで、アプリケーションの正確性が保証され、実行時にサードパーティ・ライブラリとの互換性が確保されます。
メタデータは、次の方法でnative-image
ビルダーに提供できます:
- ネイティブ・バイナリのビルド時にコード内のメタデータを計算し、必要な要素をネイティブ・バイナリの初期ヒープに格納する。
- クラスパスのMETA-INF/native-image/<groupId>/<artifactId>/ディレクトリに格納されているreachability-metadata.jsonファイルを指定する。アプリケーションのメタデータを自動的に収集する方法の詳細は、メタデータの自動収集に関する項を参照してください。
- クラスパス・スキャンまたはビルド時の初期化が必要とされるより高度なユース・ケースでは、ネイティブ・イメージ機能APIを使用する。
ノート: ネイティブ・イメージは、到達可能性メタデータのよりユーザーフレンドリな実装に移行しており、早い段階で問題が表示され、デバッグが容易になります。
アプリケーションで新しいユーザーフレンドリな到達可能性メタデータ・モードを有効にするには、ビルド時にオプション
--exact-reachability-metadata
を渡します。具象パッケージに対してのみユーザーフレンドリ・モードを有効にするには、--exact-reachability-metadata=<comma-separated-list-of-packages>
を渡します。正確な動作にコミットせずに、登録の欠落が発生しているコード内のすべての場所を全体的に把握するには、アプリケーションの起動時に
-XX:MissingRegistrationReportingMode=Warn
を渡します。アプリケーションが(
catch (Throwable t)
ブロックで)登録の欠落エラーを誤って無視している場所を検出するには、アプリケーションの起動時に-XX:MissingRegistrationReportingMode=Exit
を渡します。その後、アプリケーションはスタック・トレースとともにエラー・メッセージを無条件に出力し、即座に終了します。この動作は、すべてのメタデータが含まれることを保証するアプリケーション・テストの実行に最適です。リフレクションのユーザーフレンドリな実装は、GraalVMの将来のリリースでデフォルトになるため、プロジェクトの破損を回避するために適切なタイミングで採用することが重要です。
目次
- コードでのメタデータの計算
- JSONを使用したメタデータの指定
- メタデータ・タイプ
- リフレクション(動的プロキシを含む)
- Java Native Interface
- リソース
- リソース・バンドル
- シリアライズ
- サンプルの到達可能性メタデータ
- 実行時のクラスの定義
コードでのメタデータの計算
コードでのメタデータの計算は、次の2つの方法で実現できます:
-
JVMの要素に動的にアクセスする関数に定数引数を指定します。たとえば、次のコードの
Class#forName
を参照してください:class ReflectiveAccess { public Class<Foo> fetchFoo() throws ClassNotFoundException { return Class.forName("Foo"); } }
ここで、
Class.forName("Foo")
はビルド時に定数に評価されます。ネイティブ・バイナリがビルドされると、この値はその初期ヒープに格納されます。クラスFoo
が存在しない場合、Class#forName
へのコールはthrow ClassNotFoundException("Foo")
に変換されます。定数は次のように定義されます:
- リテラル(たとえば、
"Foo"
または1
)。 - ビルド時に初期化される静的フィールドへのアクセス。
- 実質的final変数へのアクセス。
- 長さが定数で、すべての値が定数である配列の定義。
- 他の定数に対する単純な計算(たとえば、
"F"
+"oo"
または配列への索引付け)。
定数配列を渡す場合、配列を宣言および移入する次のアプローチは、
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でメタデータを指定できない場合に適しています。これは次の場合に必要です:
- ユーザー・コードは、新しいクラス・バイトコードを生成する必要があります。
- ユーザー・コードは、アプリケーションに必要な動的にアクセスされるプログラム要素を計算するために、クラスパスをトラバースする必要があります。
次の例では、
class InitializedAtBuildTime { private static Class<?> aClass; static { try { aClass = Class.forName(readFile("class.txt")); } catch (ClassNotFoundException e) { throw RuntimeException(e); } } public Class<?> fetchFoo() { return aClass; } }
動的にアクセスされる要素は、ヒープのその部分が包含メソッド(InitializedAtBuildTime#fetchFoo
など)または静的フィールド(InitializedAtBuildTime.aClass
など)を介してアクセス可能な場合にのみ、ネイティブ実行可能ファイルのヒープに含まれます。
JSONを使用したメタデータの指定
すべてのメタデータは、META-INF/native-image/<groupId>/<artifactId>/のいずれかのクラスパス・エントリにあるreachability-metadata.jsonファイルに指定されています。到達可能性メタデータのJSONスキーマは、reachability-metadata-schema-v1.0.0.jsonに定義されています。
サンプルのreachability-metadata.jsonファイルは、サンプルの項にあります。reachability-metadata.json構成には、メタデータのタイプごとに1つのフィールドを持つ単一のオブジェクトが含まれています。最上位オブジェクト内の各フィールドには、メタデータ・エントリの配列が含まれます:
{
"reflection":[],
"resources":[],
"bundles":[],
"serialization":[],
"jni":[]
}
たとえば、Javaリフレクション・メタデータはreflection
で指定され、サンプル・エントリは次のようになります:
{
"reflection": [
{
"type": "Foo"
}
]
}
条件付きメタデータ・エントリ
JSONベースのメタデータ内の各エントリは、ネイティブ・バイナリのサイズが不必要に大きくならないように条件付きである必要があります。条件付きエントリは、次のようにエントリにcondition
フィールドを追加することによって指定されます:
{
"condition": {
"typeReached": "<fully-qualified-class-name>"
},
<metadata-entry>
}
typeReached
条件を持つメタデータ・エントリは、指定した完全修飾型に実行時に到達した場合にのみ、実行時に使用可能とみなされます。その前は、metadata-entry
で表される要素へのすべての動的アクセスは、metadata-entry
が存在しないかのように動作します。つまり、これらの動的アクセスによって、登録の欠落エラーがスローされます。
実行時に型に到達すると、その直後にその型(クラスまたはインタフェース)のクラス初期化ルーチンが開始されるか、型のサブタイプのいずれかに到達します。次の例でメタデータ・エントリを保護している"typeReached": "ConditionType"
については、その型に到達したとみなされます:
class SuperType {
static {
// ConditionType reached (subtype reached) => metadata entry available
}
}
class ConditionType extends SuperType {
static {
// ConditionType reached (before static initializer) => metadata entry available
}
static ConditionType singleton() {
// ConditionType reached (already initialized) => metadata entry available
}
}
public class App {
public static void main(String[] args) {
// ConditionType not reached => metadata entry not available
ConditionType.class;
// ConditionType not reached (ConditionType.class doesn't start class initialization) => metadata entry not available
ConditionType.singleton();
// ConditionType reached (already initialized) => metadata entry available
}
}
initialize-at-build-time
とマークされている場合、またはそのサブタイプのいずれかがinitialize-at-build-time
とマークされ、クラスパスに存在する場合も、型に到達します。
配列型は到達済としてマークされないため、条件では使用できません。
ビルド時に完全修飾型に到達可能な場合は、条件付きメタデータ・エントリがイメージに含まれます。このエントリはイメージ・サイズに影響し、実行時に条件に達した場合にのみ使用可能になります。
メタデータ・ファイルのその他の例は、GraalVM到達可能性メタデータ・リポジトリを参照してください。
メタデータ・タイプ
ネイティブ・イメージは、次のタイプの到達可能性メタデータを受け入れます:
- Javaリフレクション(
java.lang.reflect.*
API)を使用すると、Javaコードでは、実行時に独自のクラス、メソッド、フィールドおよびそれらのプロパティを調べることができます。 - JNIを使用すると、ネイティブ・コードは実行時にクラス、メソッド、フィールドおよびそれらのプロパティにアクセスできます。
- リソースでは、クラスパスに存在する任意のファイルにアプリケーションで動的にアクセスできます。
- リソース・バンドルJavaローカライゼーション・サポート(
java.util.ResourceBundle
)では、JavaコードでL10Nリソースをロードできます。 - シリアライズでは、ストリームとの間でJavaオブジェクトの書込み(および読取り)が可能です。
- (試験段階)事前定義済クラスは、動的に生成されたクラスをサポートします。
リフレクション
このセクションのすべてのメソッドについて、ネイティブ・イメージは、すべてのコール引数が定数である場合、ビルド時に到達可能性を計算します。コードに定数引数を指定することは、外部JSONファイルの情報の複製を必要としないため、メタデータを提供するために推奨される方法です。
Javaのリフレクションは、メソッドやフィールドなどの追加のリフレクティブ要素をフェッチできるjava.lang.Class
で始まります。クラスは、java.lang.Class
の次の静的関数を介してリフレクティブにフェッチできます:
java.lang.Class forName(java.lang.String) throws java.lang.ClassNotFoundException
java.lang.Class forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException
java.lang.Class forName(java.lang.Module, java.lang.String)
java.lang.Class arrayType()
- 配列型のメタデータが必要です。クラスは、java.lang.ClassLoader#loadClass(String)
で名前からクラスをリフレクティブにロードしてフェッチすることもできます。
Class
をリフレクティブにフェッチするコールにメタデータを提供するには、reachability-metadata.jsonのreflection
配列に次のエントリを追加する必要があります:
{
"type": "FullyQualifiedReflectivelyAccessedType"
}
プロキシ・クラスの場合、java.lang.Class
は、java.lang.reflect.Proxy
の次のメソッドを使用してフェッチされます:
java.lang.Class getProxyClass(java.lang.ClassLoader, java.lang.Class[]) throws java.lang.IllegalArgumentException
java.lang.Object newProxyInstance(java.lang.ClassLoader, java.lang.Class[], java.lang.reflect.InvocationHandler)
プロキシ・クラスのメタデータは、プロキシを定義するインタフェースの順序付けされたコレクションの形式です:
{
"type": {
"proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"]
}
}
前述のメソッドを、指定されたメタデータなしで呼び出すと、java.lang.Error
を拡張するMissingReflectionRegistrationError
がスローされ、処理できません。クラスパスに型が存在しない場合でも、前述のメソッドはMissingReflectionRegistrationError
をスローすることに注意してください。
java.lang.Class
の次のメソッドは、指定されたタイプにメタデータが指定されていない場合、MissingRegistrationError
をスローします:
Constructor getConstructor(Class[]) throws NoSuchMethodException,SecurityException
Constructor getDeclaredConstructor(Class[]) throws NoSuchMethodException,SecurityException
Constructor[] getConstructors() throws SecurityException
Constructor[] getDeclaredConstructors() throws SecurityException
Method getMethod(String,Class[]) throws NoSuchMethodException,SecurityException
Method getDeclaredMethod(String,Class[]) throws NoSuchMethodException,SecurityException
Method[] getMethods() throws SecurityException
Method[] getDeclaredMethods() throws SecurityException
Field getField(String) throws NoSuchFieldException,SecurityException
Field getDeclaredField(String) throws NoSuchFieldException,SecurityException
Field[] getFields() throws SecurityException
Field[] getDeclaredFields() throws SecurityException
RecordComponent[] getRecordComponents()
Class[] getPermittedSubclasses()
Object[] getSigners()
Class[] getNestMembers()
Class[] getClasses()
Class[] getDeclaredClasses() throws SecurityException
さらに、java.lang.invoke.MethodHandles.Lookup
を介したすべてのリフレクティブ・ルックアップでは、型が存在するためのメタデータも必要です。それがないと、MissingReflectionRegistrationError
がスローされます。
ラムダ・プロキシ・クラスでは、メタデータを指定できないことに注意してください。これは既知の問題であり、GraalVMの将来のリリースで対処される予定です。
リフレクティブなメソッド呼出し
メソッドをリフレクティブに呼び出すには、メソッド・シグネチャをtype
メタデータに追加する必要があります:
{
"type": "TypeWhoseMethodsAreInvoked",
"methods": [
{"name": "<methodName1>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]},
{"name": "<methodName2>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]}
]
}
便宜上、reachability-metadata.jsonに次を追加することで、メソッド・グループに対するメソッド呼出しを許可できます:
{
"type": "TypeWhoseMethodsAreInvoked",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
allDeclaredConstructors
およびallDeclaredMethods
は、指定された型で宣言されたメソッドの呼出しを許可します。allPublicConstructors
およびallPublicMethods
は、型およびそのすべてのスーパータイプで定義されたすべてのパブリック・メソッドの呼出しを許可します。
メソッド呼出しのメタデータがない場合、次のメソッドはMissingReflectionRegistrationError
をスローします:
java.lang.reflect.Method#invoke(Object, Object...)
java.lang.reflect.Constructor#newInstance(Object...)
java.lang.invoke.MethodHandle#invokeExact(Object...)
java.lang.invoke.MethodHandle#invokeWithArguments
(すべてのオーバーロード・バージョン)
リフレクティブなフィールド値アクセス
フィールド値にリフレクティブにアクセス(取得または設定)するには、フィールド名に関するメタデータを型に追加する必要があります:
{
"type": "TypeWhoseFieldValuesAreAccessed",
"fields": [{"name": "<fieldName1>"}, {"name": "<fieldNameI>"}, {"name": "<fieldNameN>"}]
}
便宜上、reachability-metadata.jsonに次を追加することで、すべてのフィールドに対するフィールド値アクセスを許可できます:
{
"type": "TypeWhoseFieldValuesAreAccessed",
"allDeclaredFields": true,
"allPublicFields": true
}
allDeclaredFields
は、指定された型で宣言されたすべてのフィールドへのアクセスを許可します。allPublicFields
は、指定された型およびそのすべてのスーパータイプのすべてのパブリック・フィールドへのアクセスを許可します。
フィールド値アクセスのメタデータがない場合、次のメソッドはMissingReflectionRegistrationError
をスローします:
java.lang.reflect.Field#get(Object)
java.lang.reflect.Field#set(Object, Object)
java.lang.reflect.VarHandle
のすべてのアクセサ・メソッド。
型の安全でない割当て
sun.misc.Unsafe#allocateInstance(Class<?>)
を介して、またはネイティブ・コードからAllocObject(jClass)
を介して、型の安全でない割当てを行うには、次のメタデータを指定する必要があります:
{
"type": "FullyQualifiedUnsafeAllocatedType",
"unsafeAllocated": true
}
そうしないと、これらのメソッドはMissingReflectionRegistrationError
をスローします。
リフレクション・メタデータのサマリー
JSONの型の全体的な定義は、次の値のようになります:
{
"condition": {
"typeReached": "<condition-class>"
},
"type": "<class>|<proxy-interface-list>",
"fields": [
{"name": "<fieldName>"}
],
"methods": [
{"name": "<methodName>", "parameterTypes": ["<param-type>"]}
],
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true,
"allDeclaredFields": true,
"allPublicFields": true,
"unsafeAllocated": true
}
Java Native Interface
Java Native Interface (JNI)を使用すると、ネイティブ・コードは任意のJavaタイプおよびタイプ・メンバーにアクセスできます。ネイティブ・イメージでは、このようなネイティブ・コードのルックアップ、書込みまたは呼出しを予測できません。JNIを使用してJava値にアクセスするJavaアプリケーションのネイティブ・バイナリをビルドするには、JNIメタデータが必要です。
たとえば、次のC
コードの場合:
jclass clazz = FindClass(env, "jni/accessed/Type");
jni.accessed.Type
クラスをルックアップし、このクラスを使用してjni.accessed.Type
のインスタンス化、メソッドの呼出し、またはフィールドへのアクセスを行うことができます。
前述のコールに対するメタデータ・エントリは、reachability-metadata.jsonを介してのみ指定できます。jni
フィールドにtype
エントリを指定します:
{
"jni":[
{
"type": "jni.accessed.Type"
}
]
}
型のメタデータを追加しても、GetFieldID
、GetStaticFieldID
、GetStaticMethodID
およびGetMethodID
を使用してすべてのフィールドおよびメソッドをフェッチすることはできません。
フィールド値にアクセスするには、フィールド名を指定する必要があります:
{
"type": "jni.accessed.Type",
"fields": [{"name": "value"}]
}
すべてのフィールドにアクセスするには、次の属性を使用します:
{
"type": "jni.accessed.Type",
"allDeclaredFields": true,
"allPublicFields": true
}
allDeclaredFields
は、指定された型で宣言されたすべてのフィールドへのアクセスを許可します。allPublicFields
は、指定された型およびそのすべてのスーパータイプのすべてのパブリック・フィールドへのアクセスを許可します。
JNIからJavaメソッドをコールするには、メソッド・シグネチャのメタデータを指定する必要があります:
{
"type": "jni.accessed.Type",
"methods": [
{"name": "<methodName1>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]},
{"name": "<methodName2>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]}
]
}
便宜上、次を追加することで、メソッド・グループに対するメソッド呼出しを許可できます:
{
"type": "jni.accessed.Type",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
allDeclaredConstructors
およびallDeclaredMethods
は、指定された型で宣言されたメソッドの呼出しを許可します。allPublicConstructors
およびallPublicMethods
は、型およびそのすべてのスーパータイプで定義されたすべてのパブリック・メソッドの呼出しを許可します。
AllocObject
を使用して型のオブジェクトを割り当てるには、メタデータをreflection
セクションに格納する必要があります:
{
"reflection": [
{
"type": "jni.accessed.Type",
"unsafeAllocated": true
}
]
}
ネイティブ・コードから動的にアクセスされる要素にメタデータを指定できないと、例外(MissingJNIRegistrationError
)が発生します。
JNIを使用するほとんどのライブラリは例外を適切に処理しないため、どの要素が欠落しているかを確認するには、
-XX:MissingRegistrationReportingMode=Warn
と--exact-reachability-metadata
を組み合せて使用する必要があります。
リソース
Javaは、アプリケーション・クラスパスの任意のリソース、またはリクエスト元のコードがアクセス権限を持つモジュール・パスにアクセスできます。リソース・メタデータは、指定されたリソースおよびリソース・バンドルを生成されたバイナリに含めるようにnative-image
ビルダーに指示します。このアプローチの結果、構成にリソースを使用するアプリケーションの一部(ロギングなど)は、ビルド時に効果的に構成されます。
ネイティブ・イメージは、次のようなjava.lang.Class#getResource
およびjava.lang.Class#getResourceAsStream
へのコールを検出します:
- これらのメソッドがコールされるクラスは定数です
- 最初の引数(
name
)は定数で、そのようなリソースが自動的に登録されます。
次のコードは、何も設定しなくてもそのまま機能します。理由は次のとおりです:
- レシーバとしてクラス・リテラル(
Example.class
)を使用しています name
パラメータとして文字列リテラルを使用していますclass Example { public void conquerTheWorld() { InputStream plan = Example.class.getResourceAsStream("plans/v2/conquer_the_world.txt"); } }
JSONでのリソース・メタデータ
リソース・メタデータは、reachability-metadata.jsonファイルのresources
フィールドで指定されます。リソース・メタデータの例を次に示します:
{
"resources": [
{
"glob": "path1/level*/**"
}
]
}
glob
フィールドは、リソースを指定するためにglobパターン・ルールのサブセットを使用します。リソース・パスを指定する際には、いくつかのルールに従う必要があります:
native-image
ツールは、star (*
)およびglobstar (**
)ワイルドカード・パターンのみをサポートしています。- 定義によれば、starは1つのレベルの任意の数の任意の文字に一致し、globstarは任意のレベルの任意の数の文字に一致することができます。
- starを文字どおりに処理する必要がある場合(特別な意味を持たない場合)、
\
を使用してエスケープできます(たとえば、\*
)。
- globでは、レベルは
/
で区切られたパターンの一部を表します。 - globパターンを記述するときは、次のルールに従う必要があります:
- Globは空にできません(たとえば、
""
) - Globの末尾はスラッシュ(
/
)で終えることはできません(たとえば、"foo/bar/"
) - Globでは、1つのレベルに3個以上の連続する(エスケープされていない)
*
文字を含めることはできません(たとえば、"foo/***/"
) - Globに空のレベルを含めることはできません(たとえば、
"foo//bar"
) - Globに2個の連続するglobstarワイルドカードを含めることはできません(たとえば、
"foo/**/**"
) - Globでは、globstarワイルドカードと同じレベルに他のコンテンツを含めることはできません(たとえば、
"foo/**bar/x"
)
- Globは空にできません(たとえば、
次のプロジェクト構造があるとします:
app-root
└── src
└── main
└── resources
├── Resource0.txt
└── Resource1.txt
次のことが可能です:
- glob
**/Resource*.txt
({ "glob":}
)を使用してすべてのリソースを含めます - glob
**/Resource0.txt
を使用してResource0.txtを含めます - glob
**/Resource0.txt
および**/Resource1.txt
を使用してResource0.txtおよびResource1.txtを含めます
Javaモジュールのリソース
リソースまたはリソース・バンドルごとに、リソースまたはリソース・バンドルの取得元のモジュールを指定できます。各エントリの個別のmodule
フィールドにモジュール名を指定できます。たとえば:
{
"resources": [
{
"module:": "library.module",
"glob": "resource-file.txt"
}
]
}
これにより、native-image
ツールにJavaモジュールlibrary.module
のresource-file.txtのみが含まれるようになります。他のモジュールまたはクラスパスにパターンresource-file.txtに一致するリソースが含まれている場合、library-module内のリソースのみが、ネイティブ実行可能ファイルに含まれるように登録されます。また、ネイティブ・イメージでは、モジュールが実行時にアクセス可能であることが保証されます。
次のコード・パターンの場合:
InputStream resource = ModuleLayer.boot().findModule("library.module").getResourceAsStream(resourcePath);
これは、前述のように登録されたリソースに対して常に期待どおりに機能します(モジュールに静的分析で到達可能と思われるコードが含まれていない場合でも)。
埋込みリソース情報
ネイティブ実行可能ファイルに含まれていたリソースを確認するには、2つの方法があります:
- ネイティブ実行可能ファイルのビルド・レポートを生成するには、オプション
--emit build-report
を使用します。含まれているすべてのリソースに関する情報は、その「Resources」
タブにあります。 - 含まれるすべてのリソースをリストするJSONファイルembedded-resources.jsonを生成するには、オプション
-H:+GenerateEmbeddedResourcesFile
を使用します。
登録されたリソースごとに次が取得されます:
- Module (リソースがどのモジュールにも属していない場合は
unnamed
) - Name (リソース・パス)
- Origin (システム上のリソースの場所)
- Type (リソースがファイル、ディレクトリまたは欠落のいずれであるか)
- Size (実際のリソース・サイズ)
ノート: リソース・ディレクトリのサイズは、すべてのディレクトリ・エントリの名前のサイズのみを表します(コンテンツ・サイズの合計ではありません)。
リソース・バンドル
Javaローカライゼーション・サポート(java.util.ResourceBundle
)では、L10Nリソースをロードして、特定のロケール用にローカライズされたメッセージを表示できます。アプリケーションに適切なリソースおよびプログラム要素を含めることができるように、ネイティブ・イメージには、アプリケーションが使用するリソース・バンドルに関する知識が必要です。
単純なバンドルは、reachability-metadata.jsonのbundles
セクションで指定できます:
{
"bundles": [
{
"name":"your.pkg.Bundle"
}
]
}
特定のモジュールからバンドルをリクエストするには:
{
"bundles": [
{
"name":"app.module:module.pkg.Bundle"
}
]
}
デフォルトでは、リソース・バンドルは、イメージに含まれるすべてのロケールに対して含まれます。次に、バンドルの特定のロケールのみを含める方法の例を示します:
{
"bundles": [
{
"name": "specific.locales.Bundle",
"locales": ["en", "de", "sk"]
}
]
}
ロケール
ネイティブ実行可能ファイルに含めるロケールと、デフォルトにするロケールを指定することもできます。たとえば、デフォルトのロケールをスイス・ドイツ語に切り替え、フランス語と英語を含めるには、次のオプションを使用します:
native-image -Duser.country=CH -Duser.language=de -H:IncludeLocales=fr,en
ロケールは言語タグを使用して指定します。-H:+IncludeAllLocales
を介してすべてのロケールを含めることができますが、結果の実行可能ファイルのサイズが大きくなることに注意してください。
シリアライズ
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でのシリアライズ・メタデータ
シリアライズ・メタデータは、reachability-metadata.jsonのserialization
セクションで指定されます。
通常のserialized.Type
を指定するには、次を使用します
{
"serialization": [
{
"type": "serialized.Type"
}
]
}
シリアライズのプロキシ・クラスを指定するには、次のエントリを使用します:
{
"serialization": [
{
"type": {
"proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"]
}
}
]
}
まれに、アプリケーションが次を明示的にコールする場合があります:
ReflectionFactory.newConstructorForSerialization(Class<?> cl, Constructor<?> constructorToCall);
ここで渡されるconstructorToCall
は、cl
の通常のシリアライズの場合に自動的に使用されるものとは異なります。
このようなシリアライズのユース・ケースもサポートするために、カスタムconstructorToCall
を使用してクラスのシリアライズを登録できます。たとえば、org.apache.spark.SparkContext$$anonfun$hadoopFile$1
のシリアライズを可能にするには、java.lang.Object
の宣言されたコンストラクタをカスタムtargetConstructor
として使用します:
{
"serialization": [
{
"type": "<fully-qualified-class-name>",
"customTargetConstructorClass": "<custom-target-constructor-class>"
}
]
}
サンプルの到達可能性メタデータ
reachabilty-metadata.jsonで使用できるサンプルの到達可能性メタデータ構成を次に示します:
{
"reflection": [
{
"type": "reflectively.accessed.Type",
"fields": [
{
"name": "field1"
}
],
"methods": [
{
"name": "method1",
"parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]
}
],
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredFields": true,
"allPublicFields": true,
"allDeclaredMethods": true,
"allPublicMethods": true,
"unsafeAllocated": true
}
],
"jni": [
{
"type": "jni.accessed.Type",
"fields": [
{
"name": "field1"
}
],
"methods": [
{
"name": "method1",
"parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]
}
],
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredFields": true,
"allPublicFields": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
],
"resources": [
{
"module": "optional.module.of.a.resource",
"glob": "path1/level*/**"
}
],
"bundles": [
{
"name": "fully.qualified.bundle.name",
"locales": ["en", "de", "other_optional_locales"]
}
],
"serialization": [
{
"type": "serialized.Type",
"customTargetConstructorClass": "optional.serialized.super.Type"
}
]
}
実行時のクラスの定義
Javaでは、実行時にバイトコードから新しいクラスをロードすることがサポートされていますが、ネイティブ・イメージでは、すべてのクラスがビルド時に認識されている必要があるためこれは不可能です(「閉世界仮説」)。この問題を回避するため、次のオプションがあります:
- アプリケーション(またはサードパーティ・ライブラリ)を変更または再構成して、実行時にクラスを生成したり、非組込みクラス・ローダーを介してクラスをロードしたりしないようにします。
- クラスを生成する必要がある場合は、専用クラスの静的イニシャライザでビルド時にクラスを生成してください。生成されたjava.lang.Classオブジェクトは、ビルド引数として
--initialize-at-build-time=<class_name>
を渡すことで初期化される静的フィールドおよび専用クラスに格納する必要があります。 - 前述のいずれも適用できない場合は、ネイティブ・イメージ・エージェントを使用してアプリケーションを実行し、
java -agentlib:native-image-agent=config-output-dir=<config-dir>,experimental-class-define-support <application-arguments>
を使用して事前定義済クラスを収集します。実行時に、トレース中に検出されたクラスの1つと同じ名前およびバイトコードを持つクラスをロードしようとすると、事前定義済のクラスがアプリケーションに提供されます。
事前定義済のクラス・メタデータは、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"
}
]
}
]
ノート: 事前定義済クラスのメタデータは、手動で記述することを意図していません。ノート: 事前定義済クラスは、レガシー・プロジェクト用のベストエフォート・アプローチであり、動作が保証されていません。