2026/09/11 2:30
JEP 544:オンタイムコードコンパイル
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本テキストでは、トレーニングフェーズにおいてコードをネイティブ AOT(Ahead-of-Time)バイトコードにコンパイルし、AOT キャッシュに保存することで、Java アプリケーションの起動時間を大幅に削減する画期的な機構を紹介します。トレーニング実行時には
java -XX:AOTCacheOutput=app.aot を使用し、本番環境では java -XX:AOTCache=app.aot でロードすることにより、HotSpot は起動時に伝統的な再コンパイル遅延を回避して、高効率なコードを瞬時に読み込むことができます。このシステムは、既存の AOT キャッシュワークフローを拡張し、プリリンクされたクラスとプロファイリングデータとともに最適化されたネイティブコードを保存するとともに、動的なデオプティマイゼーション機能を維持することで、ワークロード条件が変更した際にもアプリケーションが実行戦略を適応させられるようにしています。Serial、Parallel、G1、ZGC ゴールバコレクションの両方において AArch64 および x64 アーキテクチャで利用可能であり(CPU アーキテクチャ/機能および GC タイプが一致する必要があるという制約があり、それ以外は HotSpot がインタープリター/JIT にフォールバックし警告を発します)、既存のアプリケーションコードまたはライブラリコードへの修正は不要です。コンパイル过程中、HotSpot は静的依存関数を持つメソッドの 2 バージョンを生成します:初期化のための安全性を確保するスロウバージョンと、初期化後に最適化されるファストバージョンです。パフォーマンスベンチマーク結果により、この方法を用いることで起動時間は 65% から 80% 削減され、定常状態での効率は 4 回目で達成されます。現状では、キャッシュサイズに関する潜在的なトレードオフを開発者が管理する必要がありますが、将来の研究では標準的なバイトコード解釈および JIT への依存度をさらに低下させることを目指しています。-XX:+PrintCompilation を使用して AOT コードの読み込みと JIT コンパイルを観察し、「AOTMode=required」を使用すると制約違反時に強制的に終了します。結局、この進歩は、企業の即座な俊敏性を提供しつつ、進化し続けるワークロードに対して必要な柔軟性を損なうことなく実現します。本文
HotSpot JVM の AOT(Ahead-of-Time)機能:起動とウォームアップの高速化
HotSpot Java 仮想マシン(JVM)において、アプリケーションのネイティブコードを即座に利用可能にし、起動時およびウォームアップ時間を短縮する仕組みです。トレーニングランでコンパイルされた成果物を AOT キャッシュに保存し、プロダクションランで活用することで、負荷変動に対応しつつ AOT と JIT の両方の利点を享受できます。
目標と非目標
目標
- アプリケーションをより迅速にピーク性能へ誘導する
- 負荷変動下でもピーク性能を維持する
- コード変更(アプリケーション、ライブラリ、フレームワーク)を不要とする
- HotSpot の設定変更を最小限に抑えつつ AOT キャッシュを利用する
- Serial, Parallel, G1, ZGCの 4 つの garbage collector を継続的にサポート
- 新しいワークフローではなく、既存の AOT キャッシュ作成ワークフローを拡張
- アプリケーション側に切り替えを意識させることなく、AOT コードから JIT コードへシームレスに遷移
- AArch64 および x64のプロセッサアーキテクチャに対応
非目標
- AOT 専用モードの提供。アプリケーションは同じラン内で AOT コードと JIT コードを動的に切り替える。
- クロスコンパイルへの対応。トレーニングランとプロダクションランでは、同一の CPU アーキテクチャおよび機能セットでの実行が必須。
- 全 CPU アーキテクチャへの即座な対応。将来的に主要アーキテクチャを通常のポート作業を通じて順次追加予定。
動機づけ:JVM のパフォーマンスフェーズ
HotSpot JVM はアプリケーション実行時、以下の 3 つの段階を通過します。
-
起動フェーズ
メソッド呼び出し、クラスのロード・リンク・初期化。main- バイトコードインタプリタによる低速な実行開始。
- プロファイリングによりホットスポット(頻発メソッド)を検出し、コンパイラ C1 で基本最適化を実施。
-
ウォームアップフェーズ
- 負荷定着に伴い、クラスロード規模が低下。
- より豊富なプロファイル情報(オブジェクト種類など)を収集。
- 統計的に最もホットなメソッドを検出し、高度なコンパイラ C2 で最適化済みネイティブコードへコンパイル。
-
ピーク性能到達フェーズ
- デフォルトのウォームアップは、CPU とメモリリソースを消耗します。
- 最終的に全てのホットメソッドが最適化され、コンパイラがアイドル状態になります。
動的コンパイルの課題と対応
- 負荷変動への対応不足: ホットスポットの変化(例:新しいリクエストタイプの出現)に伴い、JIT は「デ最適化」と「再最適化」を繰り返しますが、これにより一時的なパフォーマンス低下が発生します。
- 静的コンパイル vs 動的コンパiling:
- 静的: 即座起動・ピーク性能だが、ホットスポット変化に対応できず、ポータビリティに制限あり(OS や CPU 変更で再コンパイル必要)。
- 動的: アジリティ、ポータビリティ、プラットフォーム互換性を維持しつつ、負荷変動に適応。
結論: 静的コンパiling の起動速度向上という利点を獲得しつつ、動的コンパiling の柔軟性と互換性を保持するアプローチが必要とされました。
コンパiling作業のトレーニングランへのシフト
HotSpot は通常、起動・ウォームアップ中に以下のように「複数のボールを同時に回す」必要がありました:
- コード実行
- クラスロード・リンク・初期化
- プロファイリング
- メソッドのコンパiling(最適化)
Project Leyden の仮説: 一部の作業を実行時ではなく、事前に「トレーニングラン」で実施し結果をキャッシュに保存することで効率化できる。
具体的な実装内容
- クラスロード・リンクの先行処理 (JEP 483, JDK 24): トレーニングランで済み、起動時間を短縮。
- プロファイリングの先行処理 (JEP 515, JDK 25): トレーニングランで収集したデータを使い、C2 コンパイラを即座に動作させ、ウォームアップを短縮。
キャッシュの役割
- AOT キャッシュ: トレーニングランで生成された最適化済みネイティブコードを保存。
- フォールバック機構: 負荷変化などでキャッシュが不適切な場合や不一致時は、既存のインタプリタおよび JIT に自然に戻(透過的)。
概要と使用方法
既存の AOT キャッシュ機能を拡張し、「トレーニングラン」で生成された最適化済みネイティブコード(AOT コード)を格納する仕組みです。
- 動作原理: プロダクションランで最適化コードがリクエストされ、AOT キャッシュに一致するものがあれば即座に使用される。
- 安全性: 不一致やデ最適化時には JIT にフォールバックするため、既存のコンパイラ C1/C2 と完全な相互運用性を持つ。
コマンドラインオプション
1. AOT キャッシュの生成(トレーニングラン)
$ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App ...
※ファイル
app.aot 内に、プリリンク済みクラス、プロファイリングデータ、およびホットメソッド向けのコードが格納される。
2. AOT キャッシュの利用(プロダクションラン)
$ java -XX:AOTCache=app.aot -cp app.jar com.example.App ...
※追加の設定やオプションは不要で、デフォルトで動作します。
パフォーマンス評価
5 つのベンチマークアプリケーション(一般的な Java フレームワーク構築)を用いたテスト結果です。
起動時間改善
- AOT コードなし: キャッシュ利用で約 50%〜70% の削減。
- AOT コードあり: キャッシュ利用で約 65%〜80% の削減。
ウォームアップ時間改善(javac
ベンチマーク)
javac- 1 回目の実行: AOT コードなし(キャッシュのみ)が起動時間を約 30% 改善し、AOT コード追加でさらに向上、合計約 75% の改善。
- 復元性: AOT コードありの曲線は、4 番目イテレーション時点で既に定常状態に近い性能を示す。
詳細なデータとソースコードはプロジェクトのリポジトリをご参照ください。
AOT コードと JIT コードの違い
トレーニングランとプロダクションランの環境差により、生成されるコードには以下の違いがあります。
| 違いのポイント | AOT コードの特徴 | 解決策 |
|---|---|---|
| クラス初期化順序 | トレーニングラン時と実際の負荷パターンが異なるため、参照クラスの初期化保証が必要。 | 2 バージョン戦略: 1. 低速版(初期化保証コード付き) 2. 高速版(初期化完了後に切り替え) |
動的な値 () | トレーニングラン時はクラスが未初期化のため、値が未知。コンパイル定数として扱えない。 | 明示的ロード: フィールドを直接読み込むコードを生成。 |
これらの違いにもかかわらず、JIT コンパイラはランタイムで AOT コードの代替(劣化したコード)を生成できるため、パフォーマンス利益は損なわれません。
トレーニングランとプロダクションランの一貫性
AOT キャッシュを有効にするためには、以下の 2 つの追加制約を満たす必要があります。
- CPU アーキテクチャと機能セット: 同一のものを使用(例:AVX-512 対応 CPU で生成したコードは非対応 CPU では動作しない)。
- Garbage Collector: 同一の GC を使用する(AOT コードには GC 固有のバリアが含まれるため)。
これらの条件を満たさないと、HotSpot は警告を発行し、AOT コードをスキップして通常の JIT/インタプリタへフォールバックします。この場合でも、他のキャッシュ情報(クラス定義やプロファイルデータ)は利用され、最終的にピーク性能に到達します。
プロダクション環境での診断と制御
確認コマンド
- AOT コードのロード状況確認 (
):PrintCompilation$ java -XX:+PrintCompilation \ -XX:AOTCache=app.aot -cp app.jar com.example.App ... - 制約違反時のエラー表示 (
):AOTMode=required$ java -XX:AOTMode=required \ -XX:AOTCache=app.aot -cp app.jar com.example.App ...
トレーニングランの制御(詳細ステップ)
デフォルトは「記録モード」→「アセンブリモード」という 2 ステップを自動で行いますが、明示的に操作可能です。
1. 記録モード: アプリケーション実行を行い、設定を保存。
$ java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf \ -cp app.jar com.example.App ...
2. アセンブリモード: 保存された設定から AOT コードをコンパイルしキャッシュを作成。
$ java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf \ -XX:AOTCache=app.aot
3. コンパiling アクティビティのフィルタリング:
- JIT 活動と AOT 活動の両方を表示:
を使用。-XX:+PrintCompilation - AOT 活動のみを表示(JIT 非表示):
環境変数を使用。JDK_AOT_VM_OPTIONS
4. 機能の無効化(診断用):
- キャッシュ作成を無視して通常の動作のみ:
-XX:-AOTCodeCaching - コンパiling レポートも除外したい場合、コマンドラインまたは環境変数で制御します。
将来の作業とリスク
今後の検討事項
- 完全な AOT 依存の実現: インタプリタ/JIT を最小化する場合、キャッシュファイルサイズの増加やピーク性能の低下という課題があるため、慎重な評価が必要。
- デフォルト値の最適化: 既存オプションのデフォルト値を調整し、AOT コード生成環境での挙動を円滑にする。
- ポータビリティの拡張: 異なる機能セットを持つ CPU 間での対応のためのオプション検討(キャッシュサイズ増大やパフォーマンストレードオフ)。
リスクと前提条件
- 新規リスク: JEP 483 で指摘されたもの以外の新しいリスクはない。
- 組織原則: Java アプリケーションは、実際の振る舞いを優先し、全プロセッサ機能を活用するコンパiling を行うことを前提とする(自動的アップグレード)。
- キャッシュの有用性: トレーニングランがプロダクションランの良好な予行演習となり、類似ランには利益を与え、異質ランでも JIT で補完できることが前提。