---
title: JIT コンパイル
url: https://doc.liz6.com/ja/compilers/08-compilation-technology-applications/01-jit-compilation
locale: ja
area: compilers
tags:
- compilers
- compilation-technology-applications
date: 2026-06-30
modified: 2026-07-16
description: 実行時にバイトコードを機械語にコンパイルする。階層型コンパイルはベースラインで起動速度を確保し、最適化されたJITでピークパフォーマンスを向上させる。インラインキャッシングと推測＋デオープティマイズにより、JITはAOTでは行えない積極的な最適化を実行できる——推測が外れた場合はインタプリタ実行にフォールバックする。
---

# JIT コンパイル

> 実行時にバイトコードを機械語にコンパイルする。階層型コンパイルはベースラインで起動速度を確保し、最適化されたJITでピークパフォーマンスを向上させる。インラインキャッシングと推測＋デオープティマイズにより、JITはAOTでは行えない積極的な最適化を実行できる——推測が外れた場合はインタプリタ実行にフォールバックする。

## 概要

従来のコンパイル（AOT）は、プログラム実行前にすべての機械コードを生成する。JIT（Just-In-Time）は、**実行時**にバイトコードやIR（中間表現）を機械コードにコンパイルする——「このコードが実際に呼び出された場合」のみコンパイルを行う。これにより、2つの独自の利点が生まれる。**実行時の情報**（どの分岐がホットか、どの型が実際に使用されているかを知ることができる）と、**未実行のコードのコンパイルが不要**（コンパイル範囲を縮小し、起動を高速化する）。その代償として、コンパイル時間が実行時間に含まれるため、JIT設計の核心的なジレンマは「コンパイルの品質 vs コンパイルのオーバーヘッド」にある——このエントリでは、JITが**階層型コンパイル**と**推測＋デオープティマイズ**を用いてこのジレンマをどのように解決するかを解説する。

## 階層型コンパイル：単一のJITではなく、補完的な複数のコンパイラ

現代のJITは単一のコンパイラではなく、階層化されている：

```
baseline/JIT (Ignition/V8、または C1/HotSpot):
  → 高速コンパイル、ほぼ最適化なし
  → 各関数は1回だけコンパイルされ、コンパイル時間はインタプリタ実行で節約される時間より << 短い

最適化JIT (TurboFan/V8、または C2/HotSpot):
  → 「ホットスポット」関数のみ（多数呼び出された関数、またはループを多数反復した関数）
  → 一連の最適化を実行：インライン展開、GVN、LICM、ループ展開、SROA
  → コンパイル時間は長いが、ホットスポット関数の実行比率が高いため、価値がある
```

呼び出しカウントとループバックエッジカウントが「どの階層に昇格させるか」を決定する：

```
counter[function] += 1                                  ← 呼び出しごとに1加算
if counter[function] >= threshold_baseline:
    baseline code としてコンパイル

backedge_counter[loop] += 1                             ← バックエッジごとに1加算
if backedge_counter[loop] >= threshold_optimized:
    最適化コンパイラへ移行
```

閾値は動的に調整される（V8のTurboFanは起動フェーズで閾値を緩め、コールドスタートを高速化し、定常状態に入ると閾値を下げ、ホットスポットをより早く最適化する）。

## インラインキャッシング：多相呼び出しの高速パス

JITの最も中核的な最適化の一つで、「同じ `obj.method()` 呼び出しが1000回行われ、毎回同じ型である」といったシナリオを対象とする：

<svg viewBox="0 0 720 320" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="通常の呼び出しパスとインラインキャッシュパスの比較">
  <rect width="720" height="320" fill="#ffffff"/>
  <defs>
    <marker id="jit-ic-ah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">通常パス vs インラインキャッシュ(IC)パス</text>

  <text x="40" y="86" font-size="13" font-weight="700" fill="#64748b">通常パス</text>
  <text x="40" y="100" font-size="11" fill="#94a3b8">(ICなし)</text>
  <rect x="118" y="68" width="120" height="38" rx="5" fill="#e2e8f0"/>
  <text x="178" y="92" text-anchor="middle" font-size="11.5" fill="#334155">objの型を検索</text>
  <line x1="238" y1="87" x2="260" y2="87" stroke="#475569" stroke-width="1.6" marker-end="url(#jit-ic-ah)"/>
  <rect x="264" y="68" width="176" height="38" rx="5" fill="#e2e8f0"/>
  <text x="352" y="92" text-anchor="middle" font-size="11.5" fill="#334155">型テーブルでmethodのオフセットを検索</text>
  <line x1="440" y1="87" x2="462" y2="87" stroke="#475569" stroke-width="1.6" marker-end="url(#jit-ic-ah)"/>
  <rect x="466" y="68" width="120" height="38" rx="5" fill="#e2e8f0"/>
  <text x="526" y="92" text-anchor="middle" font-size="11.5" fill="#334155">オフセットへジャンプ</text>

  <text x="40" y="196" font-size="13" font-weight="700" fill="#0f766e">インラインキャッシュパス</text>
  <text x="40" y="210" font-size="11" fill="#5eead4">(ICあり)</text>
  <rect x="118" y="172" width="150" height="46" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="193" y="191" text-anchor="middle" font-size="11" font-weight="700" fill="#115e59">obj.type ==</text>
  <text x="193" y="206" text-anchor="middle" font-size="11" font-weight="700" fill="#115e59">prev_type ?</text>

  <line x1="268" y1="182" x2="304" y2="140" stroke="#0d9488" stroke-width="1.6" marker-end="url(#jit-ic-ah)"/>
  <text x="292" y="150" font-size="10.5" fill="#0d9488">ヒット &gt;95%</text>
  <rect x="308" y="118" width="230" height="40" rx="6" fill="#0d9488"/>
  <text x="423" y="136" text-anchor="middle" font-size="11.5" font-weight="700" fill="#ffffff">prev_offsetのコードへ直接ジャンプ</text>
  <text x="423" y="151" text-anchor="middle" font-size="10.5" fill="#ccfbf1">高速パス</text>

  <line x1="268" y1="206" x2="304" y2="228" stroke="#f97316" stroke-width="1.6" marker-end="url(#jit-ic-ah)"/>
  <text x="292" y="222" font-size="10.5" fill="#c2410c">ミス</text>
  <rect x="308" y="208" width="160" height="40" rx="6" fill="#ffedd5"/>
  <text x="388" y="225" text-anchor="middle" font-size="11" font-weight="700" fill="#9a3412">型テーブルを検索</text>
  <text x="388" y="240" text-anchor="middle" font-size="10.5" fill="#c2410c">(低速パス slow path)</text>
  <line x1="468" y1="228" x2="490" y2="228" stroke="#475569" stroke-width="1.6" marker-end="url(#jit-ic-ah)"/>
  <rect x="494" y="208" width="190" height="40" rx="6" fill="#ffedd5"/>
  <text x="589" y="225" text-anchor="middle" font-size="11" font-weight="700" fill="#9a3412">prev_type を更新</text>
  <text x="589" y="240" text-anchor="middle" font-size="10.5" fill="#9a3412">prev_offset</text>

  <rect x="40" y="272" width="640" height="32" rx="8" fill="#eef2ff"/>
  <text x="360" y="292" text-anchor="middle" font-size="12" fill="#3730a3">高速パスは2〜3命令（型比較＋条件分岐）のみで、ヒット率は通常 &gt;95%——本質的には「型が前回と同じ」という推測を行う</text>
</svg>

これはコンパイラ内でのみ行われるものではない——**インラインキャッシュは、機械コード内のデータ＋コードの組み合わせとして生成され**、呼び出しごとに1回の型比較＋条件分岐（2〜3命令）のみを実行し、ヒット率は通常 >95% となる。高速パスがヒットした場合、この2〜3命令を1つの**推測（speculation）**として扱う——「今回の呼び出しの型は前回と同じ」と推測する。インラインキャッシングは、多相呼び出しの典型的な推測最適化である。

## 推測 + ガード + デオープティマイズ：JITの独自の武器

AOTコンパイラは実行時の型分布を知らないため、保守的なコードしか生成できない。JITは「この変数は1000回中999回が `int` である」ことを知った後、それを**推測**して `int` として扱い、最適パス＋ガードを生成できる：

```
guard: if (typeof(x) != INT) goto deopt_entry      ← ガード
fast_code: return x + 1                             ← 推測パス

deopt_entry:
    現在の状態をスタックフレームに保存 (on-stack replacement)
    インタプリタまたはbaseline codeへ切り替え
    インタプリタ内でこの操作を再実行
```

重要なメカニズムは**デオープティマイズ（deoptimization）**である：推測が外れた場合、JITは現在の最適化フレームの状態（レジスタ、ローカル変数）を「未最適化IRがこの時点で持つべき状態」にマッピングし、インタプリタまたはbaselineへジャンプして実行を継続する必要がある。これには、コンパイラが**最適化コードから未最適化コードへのマッピングテーブル**（デオープティマイズメタデータ）を維持することが要求される——各最適化ポイントが未最適化IRのどの位置に対応し、各最適化変数がどの未最適化変数に対応するか。

### On-Stack Replacement (OSR)

デオープティマイズの特殊なケース：関数がループを実行中（10000回実行済みなど）のタイミングで最適化またはデオープティマイズがトリガーされ、**スタックフレームの途中**で実行状態を旧バージョンから新バージョンへ移行する必要がある——これがOSRである。OSRでは、JITが「実行中のコードのスタックフレームを、ターゲットコードが期待するスタックフレームへ変換」できることが要求され、これはデオープティマイズメタデータの完全性に依存する。

## コードキャッシュと断片化

JITでコンパイルされたコードは **code cache** に保存される。GCがオブジェクトを管理するように、code cacheも管理が必要である：

- **容量**: 関数を多くコンパイルしすぎるとcode cacheが満杯になる → 冷却された（ホットでない）コンパイル済みコードを破棄（Code Cache Flush）し、インタプリタ実行またはbaselineへフォールバックする。
- **断片化**: コンパイル済み関数のサイズが一定でないため、頻繁なフラッシュにより断片化が発生する → code cacheはスlabアロケータ（mallocのarenaに類似）を使用し、一般的なサイズに対して事前に分割を行う。

## 階層型とAOTの境界の曖昧化

現代のランタイムは純粋なJITではないものが多くある：

- **Android ART**: インストール時にdexバイトコードをAOTで機械コードにコンパイル（インストール時間を利用）し、実行時はホットスポットのみをJITで補完する。
- **GraalVM Native Image**: AOTでネイティブバイナリへフルコンパイル —— JITは存在しないが、**推測最適化**（クラス階層が閉じていることが分かれば、デヴァーチャライズを行う）を利用する。
- **WasmのJIT**: ブラウザでWasmを実行する場合、まず高速なベースラインコンパイル（数ミリ秒）を行い、その後ホットスポットに対して最適化JIT（数十ミリ秒）を行う——JSのJITエンジンと同じアーキテクチャである。

JITとAOTの共通点は：**コンパイラは1つの表現から機械に近い表現への翻訳を行う**という点であり、違いは「翻訳がいつ行われるか」のみである。階層型コンパイルはこの時間を複数の層に分割し、各翻訳が最も適したタイミングで行われるようにする。

## トレードオフと失敗パターン

- **コンパイル時間が利益を食いつぶす**: 短期スクリプトは1回しか実行されないため、コンパイルがインタプリタより遅くなる → 階層型を採用し、ホットスポットのみを重コンパイルし、まずはインタプリタ実行を許容する。
- **推測の行き過ぎ**: 型推測が変な入力に対して連続してミスすると → デオープティマイズ嵐（deopt → recompile → deopt のループ）—— deopt率を監視し、閾値を超えた場合はその関数の再最適化を停止する。
- **code cacheの満杯化**: 長時間プロセスで新しいコードを継続的にコンパイルする場合 → code cacheの上限を設定し、LRUで淘汰し、満杯時はbaseline/インタプリタへ降格させる。
- **デバッグの地獄**: JITの機械コードとソースコード間のマッピングは動的である — deoptによりスタックフレームが変化し、インライン展開によりコールスタックが変化する → デバッグ情報は「最適化後のコールスタック」を「ソースレベルのコールスタック」へマッピングする必要がある（JVMの `-XX:+PrintDeoptimizationDetails`、V8の `--trace-deopt`）。

## 参考文献

- **V8**: Ignition（インタプリタ）+ Sparkplug（ベースラインJIT）+ Maglev/TurboFan（最適化JIT）— V8ブログには各層の設計ドキュメントがある
- **JVM**: HotSpot C1（クライアントコンパイラ）+ C2（サーバーコンパイラ）、`-XX:+PrintCompilation` で階層型を観察
- **Aycock (2003)**: "A Brief History of Just-In-Time" — JIT初期の学術的レビュー

*Keywords: JIT, just-in-time, method JIT, tracing JIT, tiered compilation, baseline compiler, optimizing compiler, hotspot, inline caching, hidden class, speculation, guard, deoptimization, on-stack replacement, OSR, deopt metadata, code cache, PGO, profile-guided optimization, AOT*
