ネイティブ・イメージの互換性ガイド
ネイティブ・イメージでは、従来のJava仮想マシン(VM)とは異なる方法でJavaアプリケーションをコンパイルします。ビルド時と実行時を区別します。イメージのビルド時に、native-image
ビルダーは静的分析を実行して、アプリケーションのエントリ・ポイントから到達可能なすべてのメソッドを検索します。次に、ビルダーはこれらのメソッド(これらのメソッドのみ)を実行可能バイナリにコンパイルします。このコンパイル・モデルが異なるため、ネイティブ・イメージにコンパイルすると、Javaアプリケーションの動作が若干異なる場合があります。
ネイティブ・イメージは、アプリケーションのメモリー・フットプリントの削減および起動時間の短縮のための最適化を提供します。このアプローチは、ビルド時にすべてのコードが認識される「閉世界仮説」に依存します。つまり、実行時に新しいコードはロードされません。ほとんどの最適化と同様に、すべてのアプリケーションがこのアプローチに適しているわけではありません。native-image
ビルダーがビルド時にアプリケーションを最適化できない場合、Java VMの実行を必要とするいわゆるフォールバック・ファイルが生成されます。ビルドおよび実行時にJavaアプリケーションで行われる処理の詳細は、ネイティブ・イメージの基本を確認することをお薦めします。
メタデータを必要とする機能
閉世界仮説に適したものにするために、通常、次のJava機能では、ビルド時にnative-image
に渡すメタデータが必要です。このメタデータにより、ネイティブ・イメージに必要な最小容量が使用されるようになります。
ネイティブ・イメージと最も一般的なJavaライブラリとの互換性は、最近、GitHubで共有到達可能性メタデータを公開することで強化されました。ユーザーは、サード・パーティの依存関係のメタデータを維持する負担を共有し、再利用できます。詳細は、到達可能性メタデータを参照してください。
閉世界仮説と互換性のない機能
一部のJava機能は、閉世界仮説ではまだサポートされていないため、使用するとフォールバック・ファイルが生成されます。
invokedynamic
バイトコードとメソッド・ハンドル
閉世界仮説では、コールされるすべてのメソッドとそのコール・サイトがわかっている必要があります。invokedynamic
メソッドおよびメソッド・ハンドルでは、実行時にコールを導入したり、呼び出されるメソッドを変更できます。
Javaラムダ式や文字列連結など、javac
によって生成されるinvokedynamic
ユース・ケースについては、実行時にコール対象のメソッドが変更されないという理由でサポートされています。
セキュリティ・マネージャ
信頼性の低いコードを同じプロセス内の信頼性の高いコードから分離する方法として、Javaセキュリティ・マネージャは推奨されなくなりました。その理由としては、一般的なハードウェア・アーキテクチャのほとんどは、セキュリティ・マネージャによって制限されたデータへのアクセスに対するサイドチャネル攻撃の影響を受けやすいことがあげられます。このような場合は、個別のプロセスを使用することをお薦めします。
ネイティブ・イメージでの動作が異なる可能性のある機能
ネイティブ・イメージは、Java VMとは異なるいくつかのJava機能を実装します。
シグナル・ハンドラ
シグナル・ハンドラを登録するには、シグナルを処理してシャットダウン・フックを呼び出す新しいスレッドを開始する必要があります。デフォルトでは、ユーザーが明示的に登録しないかぎり、ネイティブ・イメージのビルド時にシグナル・ハンドラは登録されません。たとえば、共有ライブラリのビルド時にデフォルトのシグナル・ハンドラを登録することはお薦めしませんが、Dockerコンテナなどのコンテナ化された環境のネイティブ実行可能ファイルのビルド時にはシグナル・ハンドラを含めることをお薦めします。
デフォルトのシグナル・ハンドラを登録するには、native-image
ビルダーに--install-exit-handlers
オプションを渡します。このオプションでは、Java VMと同じシグナル・ハンドラが提供されます。
クラス・イニシャライザ
デフォルトでは、クラスは実行時に初期化されます。これにより互換性が保証されますが、一部の最適化が制限されます。起動を高速化し、ピーク・パフォーマンスを向上させるには、ビルド時にクラスを初期化することをお薦めします。クラスの初期化動作は、特定のクラスとパッケージまたはすべてのクラスに対してオプション--initialize-at-build-time
または--initialize-at-run-time
を使用して指定できます。JDKクラス・ライブラリのメンバーであるクラスは、デフォルトで初期化されます。
ノート: ビルド時にクラスを初期化すると、既存のコードの特定の仮定が中断される場合があります。たとえば、クラス・イニシャライザにロードされたファイルは、ビルド時と実行時で同じ場所に配置されるとはかぎりません。また、ファイル記述子や実行中のスレッドなどの特定のオブジェクトは、ネイティブ実行可能ファイルに格納しないでください。このようなオブジェクトがビルド時に到達可能な場合、native image
ビルダーはエラーで失敗します。
詳細は、「ネイティブ・イメージでのクラス初期化」を参照してください。
ファイナライザ
Javaベース・クラスjava.lang.Object
では、メソッドfinalize()
が定義されています。オブジェクトへの参照はもうないとガベージ・コレクションによって判断されたときに、そのオブジェクトに対してガベージ・コレクタによってコールされます。サブクラスはfinalize()
メソッドをオーバーライドして、システム・リソースを破棄したり、その他のクリーンアップ操作を行ったりすることができます。
ファイナライザは、Java SE 9以降で非推奨になりました。これらは実装が複雑で、セマンティクスが不適切に設計されています。たとえば、ファイナライザは、その参照を静的フィールドに格納することで、オブジェクトを再度到達可能にできます。したがって、ファイナライザは呼び出されません。ファイナライザは、弱い参照および参照キューに置き換えることをお薦めします。
スレッド
ネイティブ・イメージでは、java.lang.Thread
に対してThread.stop()
などの長らく非推奨のままのメソッドが実装されることはありません。
安全でないメモリー・アクセス
sun.misc.Unsafe
を使用してアクセスされるフィールドは、静的分析のためにそのようにマークする必要があります(ビルド時にクラスが初期化される場合)。ほとんどの場合、これは自動的に行われます。static final
フィールドに格納されたフィールド・オフセットは、ホストされた値(native image
ビルダーが実行されているJava VMのフィールド・オフセット)からネイティブの実行可能値に自動的に書き換えられ、そのリライトの一部としてフィールドがUnsafe
アクセス済としてマークされます。非標準パターンの場合、フィールド・オフセットは注釈RecomputeFieldValue
を使用して手動で再計算できます。
デバッグおよびモニタリング
Javaには、JVMTIなど、Java実装でJavaプログラムのデバッグおよびモニタリングに使用できるオプション仕様がいくつかあります。ほとんどのネイティブ・イメージに発生しないコンパイルなどのイベントについて、実行時にJava VMをモニターするのに役立ちます。これらのインタフェースは、実行時にJavaバイトコードが使用可能であることを前提としています。これは、閉世界最適化でビルドされたネイティブ・イメージには当てはまりません。native-image
ビルダーではネイティブ実行可能ファイルが生成されるため、ユーザーは、Javaを対象としたツールではなく、ネイティブ・デバッガおよびモニタリング・ツール(GDBやVTuneなど)を使用する必要があります。JVMTIおよびその他のバイトコードベースのツールは、ネイティブ・イメージではサポートされていません。
Linux AArch64アーキテクチャに関する制限
次に示す制限を除き、ほとんどのネイティブ・イメージ機能はLinuxのAArch64アーキテクチャでサポートされています。
-R:[+|-]WriteableCodeCache
: 無効にする必要があります。--libc=<value>
:musl
はサポートされていません。--gc=<value>
: G1ガベージ・コレクタ(G1
)はサポートされていません。
native-image
ビルダーのオプションの完全なリストについては、こちらを参照してください。