HotSpotは実行中の観測結果を使ってJavaコードを最適化するJVM実装である

Javaのソースコードは、そのままCPUが実行できる命令ではない。 javacはソースコードをJavaバイトコードへ変換し、classファイルへ保存する。 バイトコードを読み込み、検証し、実際のCPUで動かす役割を担うのがJava仮想マシンである。

Java仮想マシンという言葉は仕様と実装の両方を指して使われやすい。 JVM仕様はclassファイルやバイトコードの動作を定め、HotSpotはその仕様に従ってプログラムを実行する具体的なソフトウェアである。 OpenJDKやOracle JDKで標準的に使われるJVM実装がHotSpotである。

Javaソースコード
       ↓ javac
classファイルのバイトコード
       ↓ HotSpot
CPUが実行できるネイティブコード

JDKは開発と実行に必要な一式を配布する単位である。 JDKにはjavacなどの開発用コマンド、Java標準ライブラリ、HotSpotなどが含まれる。 したがって、Java、JDK、JVM、HotSpotは同義ではない。

https://docs.oracle.com/en/java/javase/22/vm/java-hotspot-virtual-machine-performance-enhancements.html

HotSpotの適応的なコンパイル

HotSpotは、すべてのバイトコードを最初から最高品質のネイティブコードへ変換しない。 高度な最適化にはCPU時間とメモリが必要であり、一度しか呼ばれないメソッドまで最適化すると、実行するよりコンパイルするほうが高くつくからである。

そこでHotSpotは、起動直後のコードをインタープリターで逐次実行しながら、メソッドの呼び出し回数、ループの反復回数、実際に現れたオブジェクトの型などを観測する。 頻繁に実行される箇所をホットスポットと呼び、JITコンパイラーがそれらをネイティブコードへ変換する。 製品名のHotSpotも、この仕組みに由来する。

起動直後
  バイトコードを解釈しながら実行状況を観測する
       ↓
よく通る処理が判明する
       ↓
C1が短時間でネイティブコードを生成する
       ↓
十分な観測結果が蓄積する
       ↓
C2が時間をかけて高度に最適化する
       ↓
ピーク性能へ近づく

C1はコンパイル時間を抑えた基本的なコンパイラーであり、観測を続けるための計測処理を含むコードも生成する。 C2は蓄積した実行プロファイルを使い、メソッドのインライン展開などを施した高速なコードを生成する。 実行中にコンパイルする仕組み全体をJITコンパイルと呼ぶ。

実行時の仮定と最適化解除

C2は、現在までに観測した実行状況を根拠に大胆な最適化を行う。 たとえば、ある呼び出し先に特定の型しか現れていなければ、その型を前提として分岐やメソッド呼び出しを簡略化できる。

しかし、後から別の型が現れれば前提は崩れる。 HotSpotは古いネイティブコードの使用をやめ、安全な実行状態へ戻してから、新しい状況に合うコードを作り直す。 この処理を最適化解除と再最適化と呼ぶ。

この適応能力があるため、HotSpotは実際の本番負荷に合わせて性能を上げられる。 その代わり、起動直後は十分な観測結果とネイティブコードがなく、ピーク性能へ到達するまでにウォームアップ時間が生じる。

JEP 544が移動させる処理

JEP 544はHotSpotを別の実行方式へ置き換えない。 訓練実行でHotSpotが収集した実行プロファイルと生成したネイティブコードをAOTキャッシュへ保存し、本番実行の起動直後から再利用できるようにする。

従来
本番起動 → 観測 → JITコンパイル → ピーク性能

JEP 544
訓練実行 → 観測とコンパイル → AOTキャッシュ
                                      ↓
本番起動 ─────────────────────→ 保存済みコードを利用

本番の負荷が訓練時と異なる場合は、HotSpotが従来どおり最適化解除とJITコンパイルを行う。 JEP 544の狙いはJITコンパイルを廃止することではなく、過去の実行で済ませた作業を次の起動へ持ち越しながら、HotSpotの適応能力を残すことにある。

https://openjdk.org/jeps/544

jep-544-adds-optimized-native-code-to-the-hotspot-aot-cache

#java #jvm #hotspot #jit #aot