JIT コンパイラーによるコードの最適化方法

コンパイルするメソッドが選択されると、JVM はバイトコードを Just-In-Time (JIT) コンパイラーに送ります。 メソッドを正確にコンパイルするために、JIT はバイトコードの意味構造と構文を理解する必要があります。

JIT コンパイラーがメソッドを分析するのを支援するために、バイトコードは最初にツリー と呼ばれる内部表現に再公式化されます。この表現はバイトコードよりマシン・コードに類似しています。 次に、メソッドのツリーに対して分析と最適化が実行されます。 最後に、ツリーがネイティブ・コードに変換されます。 このセクションの残りの部分では、JIT コンパイルの各フェーズを簡単に概説します。 詳しくは、 JIT または AOT の問題の診断を参照してください。

JIT コンパイラーは、複数のコンパイル・スレッドを使用して、JIT コンパイル・タスクを実行できます。 複数のスレッドを使用すると、Java アプリケーションをより迅速に開始するのに役立つ可能性があります。 実際には、システムに未使用の処理コアがある場合にのみ、複数の JIT コンパイル・スレッドでパフォーマンスが向上します。

デフォルト・コンパイル・スレッド数は JVM によって明らかにされます。 この数はシステム構成によって異なります。 結果のスレッド数が最適でない場合は、-XcompilationThreadsオプションを使用してJVMの決定をオーバーライドできます。 このオプションの使用については、 -X オプションを参照してください。
注: システムに未使用の処理コアがない場合、コンパイル・スレッドの数を増やすことでパフォーマンスが向上する可能性はほとんどありません。

コンパイルは以下のフェーズで構成されます。 ネイティブ・コードの生成以外のすべてのフェーズは、クロスプラットフォーム・コードです。

フェーズ 1 - インライン化

インライン化とは、より小規模なメソッドからなるツリーを呼び出し元のツリーにマージ (インライン化) するプロセスです。 これによって、頻繁に実行されるメソッド呼び出しの速度が向上します。 現行の最適化レベルに従い、積極度の異なる 2 つのインライン化アルゴリズムが使用されます。 このフェーズで実行される最適化では、以下のことが実行されます。
  • 小規模なインライン化
  • 呼び出しグラフのインライン化
  • テール再帰の除去
  • 仮想呼び出し保護の最適化

フェーズ 2 - ローカル最適化

ローカル最適化では、コードを小セクションごとに分析して改善します。 多くのローカル最適化では、標準的な静的コンパイラーで使用される試験済みの手法を実装します。 この最適化では、以下のことを実行します。
  • データ・フローのローカルでの分析および最適化
  • レジスター使用の最適化
  • Java イディオムの単純化
これらの手法は特に、グローバル最適化によってさらなる改善の可能性が指摘された後に繰り返し適用されます。

フェーズ 3 - 制御フローの最適化

制御フローの最適化では、メソッド (またはメソッドの特定セクション) 内における制御のフローを分析し、コード・パスを再配置して効率を向上させます。 この最適化では、以下のことを実行します。
  • コードの再配列、分割、および削除
  • ループの縮小および反転
  • ループのストライドおよびループ不変コードの動作
  • ループのアンロールおよびピーリング
  • ループのバージョン管理および専門化
  • 例外を対象とした最適化
  • スイッチ分析

フェーズ 4 - 全体的な最適化

全体的な最適化は、メソッド全体に対して一度に処理されます。 これらの最適化はより長いコンパイル時間がかかるため「高価」ですが、パフォーマンスを大幅に増加させることができます。 この最適化では、以下のことを実行します。
  • データ・フローの全体的な分析および最適化
  • 部分的な冗長性の除去
  • エスケープ分析
  • GC およびメモリー割り振りの最適化
  • 同期の最適化

フェーズ 5 - ネイティブ・コードの生成

ネイティブ・コードの生成は、プラットフォーム・アーキテクチャーによって異なります。 一般に、コンパイルのこのフェーズ中は、メソッドからなるツリーがマシン・コード命令に変換され、アーキテクチャーの特徴に沿っていくつかの小規模な最適化が実行されます。 コンパイルされたコードは JVM プロセス・スペース (コード・キャッシュ と呼ばれる) の一部に入れられます。将来メソッドに対する呼び出しがコンパイル済みのコードを呼び出すように、コード・キャッシュにおけるメソッドの場所が記録されます。 JVM プロセスは常に、JVM 実行可能ファイルと、JVM 内のバイトコード・インタープリターに動的にリンクされている一連の JIT コンパイルされたコードで構成されています。