Run Into the New Year with Java’s Ahead-of-Time Cache Optimizations

原文はこちら。
The original article was written by Ana-Maria Mihalceanu (Senior Developer Advocate, Oracle).
https://inside.java/2026/01/09/run-aot-cache/

元々は、2025年版Java Advent Calendarの1記事です。

新年を迎え、最新のJDKリリースで追加されたAhead-of-Time (AOT) キャッシュ機能を活用し、Javaアプリケーションのパフォーマンス向上に注力しましょう。本記事では、アプリケーションにおけるAOTキャッシュ最適化の活用方法をご案内し、起動時間の短縮とピーク時のパフォーマンス向上を実現する方法を解説します。

What Is the Ahead-of-Time Cache in the JDK

JDK 24でAhead-Of-Time (AOT) キャッシュが導入されました。これはHotSpot JVMの機能であり、クラスが読み込まれ、解析、ロード、リンク後にそれらを保存します。AOTキャッシュの作成はアプリケーション固有のものであり、そのアプリケーションの次回以降の実行時に再利用することで、最初の機能単位の作業までの時間(起動時間)およびピーク性能に達するまでの時間(ウォームアップ時間)を改善できます。

AOTキャッシュを生成するには、以下の2つの手順を実行する必要があります。

  • アプリケーションの実行状況を記録するトレーニング。-XX:AOTMode=recordを設定し、設定ファイルの保存先を-XX:AOTConfigurationで指定することで記録を開始できます。
java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf \
-cp app.jar com.example.App ...

この手順は、「アプリケーションがどのクラスをロードし初期化するのか?」「どのメソッドが頻繁に呼び出されるのか?」といった質問に答えるためのもので、結果を設定ファイル(app.aotconf)に保存します。

  • その後、アセンブリは設定ファイルの観測結果をAOTキャッシュ(app.aot)に変換します。
java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf \
-XX:AOTCache=app.aot -cp app.jar

起動時間を短縮するには、アプリケーションを実行する際に、前の手順で作成したAOTキャッシュを指すように-XX:AOTCacheフラグを設定してください。

java -XX:AOTCache=app.aot -cp app.jar com.example.App ...

起動時間の改善は、通常プログラム実行時にジャストインタイムで行われる処理を、キャッシュを生成する第二段階に前倒しした結果です。これにより、第三段階ではクラスがキャッシュから即時利用できるので、プログラムの起動が高速化されます。

この三段階のワークフロー(トレーニング+アセンブル+実行)は、JEP 483: Ahead-of-Time Class Loading & Linking により、JDK 24から利用可能になりました。これはProject Leydenの研究から初めてマージされた機能です。一連のベンチマークにより、この機能およびその他のLeydenのパフォーマンス関連機能の有効性が実証されています(Fig.1)。

JEP 483: Ahead-of-Time Class Loading & Linking
https://openjdk.org/jeps/483
Project Leyden
https://openjdk.org/projects/leyden/
Benchmarking
https://github.com/openjdk/leyden/blob/634547513c2a2b707ae43a735dc24fd1977da2ae/README.md#5-benchmarking

JDK 24 AOT Cache Benchmarks Figure 1: AOT Cache Benchmarks as of JDK 24

JDK 25では、JEP 515 – Ahead-of-Time Method Profiling の変更により、頻繁に実行されるメソッドのプロファイルがAOTキャッシュの一部として扱われるようになりました。この機能追加により、アプリケーション起動時にJITが直ちにネイティブコードの生成を開始できるようになり、アプリケーションのウォームアップが改善されます。この新しいAOT機能により、アプリケーションの実行に追加の制約を加える必要はなく、既存のAOTキャッシュ作成コマンドをご利用いただけます。さらに、ベンチマークでは起動時間の改善も確認されました(Fig.2)。

JEP 515 – Ahead-of-Time Method Profiling
https://openjdk.org/jeps/515
Leyden Prototype Repository
https://github.com/openjdk/leyden/blob/634547513c2a2b707ae43a735dc24fd1977da2ae/README.md

JDK 25 AOT Cache Benchmarks Figure 2: AOT Cache Benchmarks as of JDK 25

JDK 25では、-XX:AOTCacheOutputフラグの引数を設定することで、AOTキャッシュを生成するプロセスを単一のステップで実行できるように簡素化されています。

# Training Run + Assembly Phase
java -XX:AOTCacheOutput=app.aot \
-cp app.jar com.example.App ...

-XX:AOTCacheOutput=[cache location]を指定すると、JVM はシャットダウン時にキャッシュを作成します。JEP 514 – Ahead-of-Time コマンドラインの操作性により、AOT キャッシュの作成と使用のための二段階のプロセスが導入されました。

# Training Run + Assembly Phase
java -XX:AOTCacheOutput=app.aot \
-cp app.jar com.example.App ...
# Deployment Run
java -XX:AOTCache=app.aot -cp app.jar com.example.App ...

リソースに制約のある環境では、2段階のワークフローが期待通りに動作しない可能性があります。AOTキャッシュを生成する副呼び出しは、トレーニング実行に使用されるヒープと同じサイズの独自のJavaヒープを使用します。

Thoughts on Training Runs
https://openjdk.org/projects/leyden/notes/05-training-runs

その結果、1段階のAOTキャッシュ生成を完了するために必要なメモリは、コマンドラインで指定されたヒープサイズの2倍となります。例えば、

java -XX:AOTCacheOutput=

というステップに、2GB のヒープを指定するオプション

-Xms2g -Xmx2g

が追加されている場合、ワークフローを完了するには 4GB の環境が必要です。

アプリケーションを小規模なクラウドテナントにデプロイする場合、3 段階のワークフローのようにステップを分割する方が適切な選択肢となる可能性があります。そのような場合、トレーニングは小規模なインスタンスで実行し、AOTキャッシュの作成はより大規模なインスタンスで行うことが可能です。これにより、トレーニング実行はデプロイ環境を反映しつつ、AOTキャッシュの作成では大規模インスタンスの追加CPUコアとメモリを活用できます。

いずれのワークフローを選択される場合でも、AOTキャッシュの要件と、アプリケーションのニーズに最適に設定する方法について詳しく見ていきましょう。

How to Craft the AOT Cache Your Application Needs

トレーニングと本番環境での実行は一貫した結果を生むべきであり、デプロイ実行ではより高速であることが求められます。これを実現するため、アセンブリフェーズはトレーニングと本番実行の間の処理を仲介します (Fig 3).

Train-Assembly-Deploy Fig 3: Training / Assembly / Deployment

一貫したトレーニング実行およびその後の実行において、以下の点をご確認ください。

  • トレーニング実行間でJARファイルのタイムスタンプが保持されていること。
  • トレーニング実行と本番環境では、同一のハードウェアアーキテクチャおよびOS用の、同一のJDKリリースを使用していること。
  • トレーニング実行時のアプリケーション動作が、本番環境におけるアプリケーションの想定動作と類似していること(例:トレーニング実行時に最も頻繁に使用されるアプリケーション領域が、本番環境でも最も頻繁に使用される領域であること)。
  • アプリケーションのクラスパスは、ディレクトリ、ワイルドカード、ネストされたJARファイルを含まない、JARファイルのリストとして提供していること。
  • 本番実行時のクラスパスは、トレーニング実行時のクラスパスのスーパーセットであること。
  • AddToBootstrapClassLoaderSearchおよび AddToSystemClassLoaderSearch API を呼び出す JVMTI エージェントを使用しないこと。

AOT キャッシュを使用するために JVM が正しく設定されているかどうかを確認するには、コマンドラインにオプション -XX:AOTMode=on を追加するとよいでしょう。

java -XX:AOTCache=app.aot -XX:AOTMode=on \
-cp app.jar com.example.App ...

AOTキャッシュが存在しない場合、または上記要件のいずれかを満たさない設定の場合、JVMはエラーを報告します。JDK 24および25で導入された機能はZGCをサポートしていませんでした。しかし、JEP 516 により、JDK 26以降ではこの制限はなくなります。

JEP 516: Ahead-of-Time Object Caching with Any GC
https://openjdk.org/jeps/516

本番環境でAOTキャッシュが効果的に機能するためには、トレーニング実行とそれ以降のすべての実行が本質的に同一である必要があります。トレーニング実行とは、アプリケーションが異なる実行間でどのような動作をしているかを観察する手段であり、主に以下の2種類があります。

  • ビルド時に実行される統合テスト (integration tests)
  • 本番環境でのトレーニングを必要とする本番ワークロード

トレーニングステップ中に使用していないクラスを読み込まないようにし、リッチなテストフレームワークはスキップしてAOTキャッシュを最小限に保つことをお勧めします。トレーニング時には必要なクラスをロードするために外部依存関係をモック化しますが、これにより余分なキャッシュエントリが生成される可能性がある点にご注意ください。AOTキャッシュの有効性は、トレーニング実行が本番環境の動作とどれだけ近いかによって決まります。アプリケーションを再ビルドしたりJDKをアップグレードしたりした場合は、AOTキャッシュを再生成する必要があります。

なお、AOTキャッシュはアプリケーションの現在の状態に対してのみ有効である点にもご留意ください。アプリケーションのコード変更、ライブラリの追加、既存ライブラリの更新により、キャッシュが無効になります。そのため、アプリケーションのビルドごとにキャッシュを再生成する必要があります。そうしないと、クラッシュや未定義動作(キャッシュからメソッドが欠落)のリスクがあります。問題が発生している場合や、アプリケーションの期待されるパフォーマンス向上が得られない場合は、

-Xlog:aot,class+path=info

オプションで実行し、キャッシュから何が読み込まれているかを監視してみてください。

Tips for Efficient Training Runs

パフォーマンスとトレーニングの実行容易性にはトレードオフが存在します。本番環境でのトレーニング実行は、特にログファイルの作成、ネットワーク接続の確立、データベースへのアクセスなどを行うサーバーアプリケーションにおいては、必ずしも現実的ではありません。このようなケースでは、実際の本番環境の実行に可能な限り近い合成トレーニング実行を作成することが望ましいです。

トレーニング実行を本番環境と同じクラスのロードに合わせることで、起動時間の最適化が図れます。トレーニング実行でどのクラスがロードされるかを確認するには、起動時に

-verbose:class

フラグを追加してください。あるいは、jdk.ClassLoad JFRイベントを有効にし、アプリケーションのプロファイリングを行うことでロードされたクラスを観察することもできます。

# configure the event
jfr configure jdk.ClassLoad#enabled=true
# profile as soon as your application launches
java -XX:StartFlightRecording:settings=custom.jfc,duration=60s,filename=/tmp/AOT.jfr

記録ファイルを取得したら、読み込まれたクラスを確認できるだけでなく、以下のjfrコマンドを実行して、アプリケーションが頻繁に使用するメソッドも確認できます。

# print jdk.ClassLoad events from a recording file
jfr print --events "jdk.ClassLoad" /tmp/AOT.jfr
# view frequently executed methods
jfr view hot-methods /tmp/AOT.jfr

頻繁に使用されるメソッドがあるけれどもトレーニング実行時に検出されなかったことがわかった場合は、それらのメソッドをトレーニング対象にしてください。必要に応じて、一時ファイルディレクトリ、ローカルネットワーク構成、および模擬データベースを使用して、アプリケーションの標準的な動作モードを検証できます。トレーニング中に未使用クラスを読み込むことは避け、AOTキャッシュを最小限に保つため、リッチなテストフレームワークは使用しないでください。代わりに、典型的な起動パスをカバーする簡易テスト(スモークテスト)を使用し、大規模なテストスイートや負荷テスト/回帰テストを避けてください。

Takeaways

結論として、AOTキャッシュを最適化しパフォーマンスを向上させるには、以下の点にご留意ください。

  • キャッシュの有効期限または陳腐化:アプリケーションの再構築やJDKのアップグレード時には、AOTキャッシュを再生成する必要があります。
  • 移植性:AOTキャッシュはJVMおよびプラットフォーム固有です。
  • 起動パスのカバレッジ:トレーニング実行では、典型的なアプリケーション起動パスを網羅している必要があります。トレーニング実行が浅い場合、十分にウォームアップされず、キャッシュの効果が限定的になります。
  • 運用環境の設定:アプリケーションJARとAOTキャッシュの両方が、最小権限で実行され、不変のインフラストラクチャ慣行に従う必要があります。運用環境設定:アプリケーションJARとJDKの両方を対象とする必要があります。

ソフトウェアは進化し続けるため、アプリケーションのパフォーマンスは継続的な課題です。新機能の追加、ライブラリやフレームワークの変更、ワークロードの増加、インフラストラクチャの移行(例:クラウド、コンテナオーケストレーションなど)が発生します。そして、これらの進化に応じて、アプリケーションのパフォーマンス目標も変化します。アプリケーションのトレーニングに投資し、JDKのリリースに追従して利用可能な最適化を解き放ちましょう。パフォーマンスは各リリースで向上していきます!

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください