10 人が閲覧(直近 30 日) プログラミング言語

JITコンパイラとは?仕組み・インタプリタやAOTとの違いと主要言語の実装

JITコンパイラとは?仕組み・インタプリタやAOTとの違いと主要言語の実装

JITコンパイラ(Just-In-Time compiler、実行時コンパイラ)は、プログラムを実行しながら機械語に変換して速くする仕組みです。Javaの仮想マシン、ブラウザのJavaScriptエンジン、Ruby・PHP・Pythonの処理系、さらにPostgreSQLのようなデータベースや正規表現エンジンにも入っています。

この記事では、インタプリタやAOT(事前コンパイル)との違い、速くなる仕組み、言語ごとの現状を整理します。あわせて、同じ関数をJITの有無で走らせた実行時間を6つの処理系で載せます(2026年10月21日に計測)。

まとめ:JITコンパイラの要点

  • JITコンパイラは実行中にコードを機械語へ変換する。HotSpotやV8は何度も呼ばれる関数や長く回るループを選んで変換するので、起動直後はインタプリタで動き、温まってから速くなる
  • AOTは実行前に変換するので、実行時のコンパイルが要らず短命な処理で有利になりやすい。JITは実行中の型や分岐の傾向を使えるので、長時間動くプロセスのピーク性能で有利になりやすい
  • Java・JavaScript・.NETはJITが既定で動く。Ruby・PHP・Pythonは既定で無効で、自分で有効にする必要がある
  • fib(32)の実測では、JITの有無で中央値に約21倍(Java)、約10倍(Node.js)、約4.9倍(Ruby)、約4.8倍(LuaJIT)、約3.0倍(PHP)の差が出た。Python 3.15.0のJITは逆に約5%遅かった
  • PostgreSQLのJITは18まで既定でon、19(2026年11月19日に正式版の予定)で既定offに変わる。Homebrew版の18.6はLLVMなしでビルドされており、jitがonでも動かない
  • iOSアプリ、カバレッジ計測中のPHP、JITなしでビルドされたPythonなど、JITが使えない・切られている場面がある

JITコンパイラ(実行時コンパイラ)の意味と読み方

JITは「Just-In-Time」の略で、「ジット」または「ジェイアイティー」と読みます。日本語では実行時コンパイラ、その処理をJITコンパイルと呼びます。ソースコードやバイトコードを、実行の途中でそのCPU向けの機械語に変換する方式です。

JITの条件は「実行時に機械語へ変換する」ことだけで、何をいつ変換するかは実装によって違います。HotSpotやV8は関数の呼び出し回数やループの回転数を数え、しきい値を超えた「ホットな」部分だけをコンパイルします。一度しか通らない初期化処理まで機械語にすると、変換時間の方が高くつくためです。一方、.NETは初回の呼び出しから軽い最適化でコンパイルし、よく使われるメソッドを後から最適化し直します。

製造業の「ジャストインタイム生産方式」も同じJITと略しますが、別物です。こちらを探している方はジャストインタイム(JIT)とは?JISの定義・トヨタ生産方式での起源とかんばん方式を参照してください。

JIT・インタプリタ・AOTの違い

3方式の違いは「いつ機械語にするか」です。

方式 機械語にするタイミング 起動 長時間実行時の速度 代表例
インタプリタ 変換しない(1命令ずつ解釈) 速い 遅い CPython既定、java -Xint
JIT 実行時(初回呼び出し・高頻度部分など) 温まるまで遅い 速い HotSpot、V8、YJIT、LuaJIT
AOT 実行前に全体 速い 最適化条件による C/C++、Go、GraalVM Native Image

JITはインタプリタとAOTの中間ではありません。インタプリタで動かしながら情報を集め、その情報を使って実行状況に合わせた最適化をかけるのが代表的な形です。

JITとインタプリタの命令実行方式の違い

インタプリタはバイトコードを1命令ずつ読んで対応する処理を呼ぶため、同じループを100万回回すと、読み取りと分岐の手間も100万回かかります。JITでコンパイルされたループでは、この逐次解釈の手間が消えます。HotSpotのように実行頻度を基準にする処理系では、低頻度のコードはインタプリタのまま動きます。一方、.NETのQuick JITは、ホットになる前の初回呼び出しから機械語を作ります。

JITとAOTのコンパイル時期・プロファイル利用の違い

AOTはコードを実行前に機械語へ変換するので、実行時のコンパイルを省けます。ただし起動時の初期化は残り、起動直後から最高速になるとは限りません。通常のAOTは実行中の情報に合わせて再コンパイルしませんが、PGO(プロファイルに基づく最適化)を使えば、事前の試走で集めた型や分岐の傾向をコンパイルに反映できます。JITは現在の実行中に観察した型を使って「この引数はいつも整数」と仮定した専用コードをその場で作り、仮定が外れれば作り直せます。

短命なCLIやコールドスタートが効くサーバーレス関数ではAOTが有利になりやすく、何時間も動くサーバーはJITが追いつき、追い越すこともあります。両者を混ぜる動きもあり、JDK 25ではJEP 515により、前回の実行で集めたメソッドのプロファイルを起動時から使い、JITがプロファイル収集を待たずに機械語を作れるようになりました。RubyにもSpinelのようなAOTコンパイラが登場しています(Spinelとは?MatzのRuby AOTコンパイラの仕組みと使い方、書けないRubyを実測で整理)。

JITの欠点:ウォームアップ・メモリ・攻撃面

起動直後はインタプリタや低い最適化段階の機械語で動き、コンパイル自体もCPUを使います(ウォームアップ)。生成した機械語とプロファイルを保持する分だけメモリも増えます。さらに、書き込んだメモリを実行する仕組みはブラウザの脆弱性で狙われやすく、セキュリティ重視の設定ではJITを切ります(後述)。短い処理ではコンパイルの方が高くつくこともあります。PostgreSQLの公式文書の例では、SUM(relpages)の問い合わせがJITなしで0.365ミリ秒、しきい値を下げてJITを使わせると7.416ミリ秒(うちJITが7.104ミリ秒)でした(PostgreSQL 18 文書 30.2 When to JIT?)。

JITが速くなる仕組み

JITの速さは「数える」「段階的に最適化する」「仮定が外れたら戻る」の組み合わせで生まれます。

ホットコードの検出と階層型コンパイル

多くのランタイムは、軽いコンパイラと重いコンパイラを段階的に使い分けます。JavaのHotSpotでは、レベル0がインタプリタ、レベル1〜3がC1(クライアントコンパイラ)、レベル4がC2(サーバーコンパイラ)です。C1は短時間で平凡な機械語を作りながらプロファイルを集め、十分にホットになった関数だけをC2が時間をかけて最適化します。Temurin 25.0.4.1の既定値は次のとおりです。

$ java -XX:+PrintFlagsFinal -version | grep -E "Tier[34]InvocationThreshold|TieredCompilation "
     intx Tier3InvocationThreshold = 200     {product} {default}
     intx Tier4InvocationThreshold = 5000    {product} {default}
     bool TieredCompilation        = true    {pd product} {default}

呼び出し200回程度でC1、5,000回程度でC2の対象になる目安です(実際の判定はループの回転数も合わせた式で決まります)。-XX:+PrintCompilationを付けると、関数がどのレベルでコンパイルされたかを確認できます。

$ java -XX:+PrintCompilation Fib
    149   83       3       Fib::fib (23 bytes)
    149   84       4       Fib::fib (23 bytes)
    150   83       3       Fib::fib (23 bytes)   made not entrant: not used

3列目がレベルです。起動149ミリ秒時点でレベル3(C1+プロファイル収集)、直後にレベル4(C2)でコンパイルされ、用済みになったレベル3のコードが「made not entrant」で退役しています。JVM全体の構造はJava仮想マシン(JVM)とは?仕組み・メモリ構成・JITを実機の出力で解説で扱っています。

ループ途中での切り替え(OSR)

呼び出し回数だけを数えると、mainの中で1回だけ回る長いループはいつまでもコンパイルされません。そこでHotSpotは、ループの後ろ向きジャンプ(バックエッジ)も数え、実行中のメソッドをループの途中からコンパイル済みコードへ差し替えます。これがOn-Stack Replacement(OSR)です(HotSpot FAQ)。5,000万回のループをmainに1つだけ書いて、Temurin 25.0.4.1で確かめました。

public class Loop {
  public static void main(String[] a) {
    long s = 0;
    for (int i = 0; i < 50_000_000; i++) { s += i % 7; }
    System.out.println(s);
  }
}
$ java -XX:+PrintCompilation Loop | grep Loop::main
190   80 %     3       Loop::main @ 4 (32 bytes)
191   81       3       Loop::main (32 bytes)
191   82 %     4       Loop::main @ 4 (32 bytes)
196   80 %     3       Loop::main @ 4 (32 bytes)   made not entrant: OSR invalidation of lower level
378   82 %     4       Loop::main @ 4 (32 bytes)   made not entrant: uncommon trap

%がOSRコンパイルの印で(JDK 25のソースcompileTask.cppでis_osr_methodのときに%を出す)、@ 4はループの入口のバイトコード位置です。mainは1回しか呼ばれていないのに、190ミリ秒時点でC1、その直後にC2のOSR版が作られました。最後の行の「uncommon trap」は、プロファイル上ほとんど通らないと見なして省いた経路に入ったため、そのコードを捨てたという意味です。ベンチマークで「1回のループだけ測る」コードが速く出るのはOSRのおかげで、メソッド単位のJITだと思って結果を読むと解釈を誤ります。

投機的最適化と脱最適化

JITは実行時の観察から「この引数は小さな整数」と仮定し、型チェックをガードとして残しながら、整数向けに特化したコードを作れます。仮定が外れると、ランタイムは最適化済みコードを捨ててインタプリタへ戻します。これを脱最適化(deoptimization)と呼びます。Node.js v26.5.0で、整数ばかりで呼んでいた関数に最後だけ文字列を渡すと、V8のログに脱最適化が出ます。

function add(a, b) { return a + b; }
for (let i = 0; i < 1e6; i++) add(i, 1);
add("x", "y");
$ node --trace-opt --trace-deopt deopt.js
[marking ... <JSFunction add> for optimization to MAGLEV, ... reason: hot and stable]
[bailout (kind: deopt-eager, reason: not a Smi): begin. deoptimizing ... <JSFunction add> ...]

not a Smiは「小さな整数(Small integer)のはずが違った」という意味です。型の変化は脱最適化の原因になりますが、その後に複数の型を扱うコードへ再最適化されることもあります。どこまで複数の型に対応し直すかはランタイムによって異なり、型が安定している関数ほど型特化をかけやすくなります。

メソッドJITとトレーシングJIT

何を1単位としてコンパイルするかで、JITは大きく2つに分かれます。メソッドJITは関数(メソッド)を単位にし、HotSpotやV8、.NETがこちらです。トレーシングJITは、実際に通った実行経路(トレース)を記録して、その経路だけを直線のコードにします。LuaJITは「アセンブラで書いた高速インタプリタ」と「トレースコンパイラ」の組み合わせで、PythonのPyPyはRPythonで書かれたインタプリタ自体をトレースするJITを持ちます(PyPyのアーキテクチャ文書)。

PHPは両方を選べます。opcache.jit=tracingがトレース単位、opcache.jit=functionが関数単位で、後述の計測ではtracingの方が速く出ました。CPythonもPython 3.15でJITに「新しいトレーシングのフロントエンド」を入れ、3.14より多くのバイトコードと制御フローを扱えるようになっています(What’s New In Python 3.15)。

主要言語のJIT実装(2026年10月時点)

言語・ランタイム JITの構成 既定 有効化・確認
Java(HotSpot) C1+C2の階層型 有効 -XX:+PrintCompilation
JavaScript(V8) Ignition→Sparkplug→Maglev→TurboFan 有効 node --trace-opt
.NET RyuJITの階層型+Dynamic PGO 有効 TieredPGO設定
Ruby 4.0 YJIT、ZJIT 無効 ruby --yjit
PHP 8.5 OPcacheのJIT(tracing/function) 無効 opcache.jit=tracing
Python 3.15 copy-and-patch方式(実験的) 無効 PYTHON_JIT=1
Lua(LuaJIT 2.1) トレーシングJIT 有効 luajit -joffで停止

既定で動いているのはJava・JavaScript・.NET・LuaJITで、Ruby・PHP・Pythonは自分で有効にしない限りインタプリタのまま動きます。

Java(HotSpot)のJIT

HotSpotは階層型コンパイルが既定(Java 25でもTieredCompilation = true {default})で、上で見たC1とC2を使い分けます。起動を速くしたい短命なバッチでは-XX:TieredStopAtLevel=1でC1だけに絞る方法もあります。後述のfib(32)ではC1だけで19〜36ミリ秒、C2込みで16〜35ミリ秒でした。範囲は重なりますが、中央値はそれぞれ36ミリ秒と18ミリ秒で、今回の計測では約2倍の差がありました。C2が効くかどうかは、インライン展開やエスケープ解析をかけられる処理かで変わります。

C2の代わりにJavaで書かれたGraalコンパイラを使う選択肢や、JITを使わずネイティブ実行ファイルを作るNative ImageはGraalVMとは?JVMとの違いとNative Imageの作り方【2026年版】にまとめています。

PythonのJIT(CPython・PyPy)

CPythonのJITは設計がPEP 744にまとめられており、Python 3.13ではソースからビルドするときに--enable-experimental-jitを付けた場合だけ入りました。3.14からは公式のmacOS版・Windows版インストーラーに同梱され、環境変数PYTHON_JIT=1で有効になります。方式はcopy-and-patchと呼ばれ、ビルド時にLLVMで命令ごとの機械語の「型紙」を作っておき、実行時はそれを並べて穴を埋めるだけにしています。実行環境にLLVMは要りません。

2026年10月9日にPython 3.15.0が公開されました。What’s Newは、pyperformanceの幾何平均で、x86-64 Linuxでは全最適化を有効にした標準インタプリタより7〜8%、AArch64 macOSでは末尾呼び出し型インタプリタより11〜12%速いと報告しています。同じ節には、JITの有無による差がベンチマークごとに「約15%遅い〜100%超速い」と幅があることも書かれています。JITは引き続き実験的機能で、公式バイナリでも既定は無効です。

配布元によってはJITが入っていません。uvで入れたPython 3.14.6ではis_available()がFalseで、PYTHON_JIT=1を付けても有効になりませんでした。

$ PYTHON_JIT=1 python3.14 -c "import sys; print(sys._jit.is_available(), sys._jit.is_enabled())"
False False

PythonのJITはCPythonのものだけではありません。PyPyは長くトレーシングJITで動いてきた別実装で、2026年10月14日公開のPyPy 8.0.1はPython 2.7・3.11と、3.12のベータ版を提供しています。数値計算の関数を対象にするなら、Numbaも候補です。対応するPython・NumPyの機能で書かれた計算部分に適用し、初回コンパイルの時間と、その後の実行時間を分けて評価してください。版ごとの詳細はPython 3.14のJITコンパイラとは?有効化の手順と3.15.0までの実測、フリースレッド版と新機能、NumbaはNumbaとは?Pythonを高速化するJITコンパイラの使い方とインストール、3.15全体の変更はPython 3.15の新機能|遅延インポートとUTF-8既定化、3.14から上げる手順を参照してください。

JavaScript(V8)のJIT

Chrome・Node.js・Denoが使うV8は、バイトコードを解釈するIgnitionの上に3段のコンパイラを重ねています。Sparkplugはバイトコードをほぼそのまま機械語に写す最も軽い層、Maglevは短時間で中程度の最適化をかける層、TurboFanは時間をかけて最大限に最適化する層です。fib関数に--trace-optを付けると、まずMaglev、続いてTurboFanへ昇格する様子がログに出ます。

$ node --trace-opt fib.js
[marking ... <JSFunction fib> for optimization to MAGLEV, ConcurrencyMode::kConcurrent, reason: hot and stable]
[completed compiling ... <JSFunction fib> (target MAGLEV) - took 0.000, 0.698, 0.002 ms]
[marking ... <JSFunction fib> for optimization to TURBOFAN_JS, ConcurrencyMode::kConcurrent, reason: hot and stable]
[completed optimizing ... <JSFunction fib> (target TURBOFAN_JS)]

Maglevのコンパイルは0.7ミリ秒ほどで終わっており、「短時間で中程度の最適化」という位置づけがログからも読み取れます。V8の実行単位であるIsolateについてはV8 Isolateとは?Contextとの違い・起動時間の実測とCloudflareの分離設計【2026年9月】で扱っています。

Ruby・PHP・.NETのJIT有効化と既定設定

Ruby 4.0にはYJITとZJITの2つのJITがあり、どちらも既定では無効です。YJITは--yjit、ZJITは--zjitで有効になります。ZJITはRuby 4.0.0で入ったメソッド単位の新しいJITで、4.0の時点ではYJITの方が速い段階です。後述のfib(32)でもZJITはインタプリタより遅い結果でした。版ごとの状況はRubyの最新バージョンは4.0.7|4.1.0-preview1の変更点・3系との違い・サポート期限(2026年10月)にあります。

PHPはPHP 8.0でOPcacheにJITが入りました。既定では無効で、PHP 8.4からは無効にする既定値がopcache.jit_buffer_size=0からopcache.jit=disableに変わりました(バッファの既定は64M)。使うにはopcache.jit=tracingなどを明示します(PHPマニュアル OPcache設定)。8.3以前の感覚でバッファサイズだけを指定しても、8.4以降はJITが動きません。DBや外部APIの待ち時間が大半を占めるWebアプリでは、PHPの実行をJITで速めても全体への効果は限られます。OPcache本体の有効化を確認したうえで、待ち時間とPHP処理時間を分けて測ってください。設定の詳細はOPcacheとは?読み方とPHP 8.5の変更点・設定・確認方法を参照してください。

.NETは.NET Core 3.0以降、RyuJITが階層型コンパイルを既定で行い、.NET 8からは実行時のプロファイルで再最適化するDynamic PGOも既定で有効です。Microsoftのランタイム設定の文書は、Dynamic PGOを「最も頻繁に使われる型やコードパスに合わせて最適化したコードを生成する」と説明しています。起動時間やメモリを詰めたい場合は、JITを使わないNative AOTを選べます(.NET 11の新機能とリリース日|サポート期限・C# 15・.NET 10との挙動差を実測【RC1】)。

同じ関数で比べたJITの有無と実行時間

JITの効果がどれくらいか、同じ再帰関数fib(32)(約700万回の関数呼び出し)を6つの処理系で測りました。条件は次のとおりです。

  • 2026年10月21日、Intel Core i9-9880H・macOS。ほかの処理が動いている状態(ロードアベレージ約3.8)で計測
  • 1回ごとにプロセスを起動し直し、全設定を交互に5周。表の範囲は5回の最小〜最大、倍率は中央値どうしの比(括弧内は中央値のミリ秒)
  • プログラム内のタイマーでfib(32)の呼び出し1回分だけを測った。どの言語も単調時計の経過時間で、起動時間は含まないが、実行中に起きるJITコンパイルの時間は含む。LuaJITはFFIでclock_gettimeを呼んで測り、ほかの5つの後に別に5周した
  • PHPはphp -n -d opcache.enable_cli=1 -d opcache.jit=tracingのようにphp.iniを読まずに実行(理由は後述のpcovの件)。Pythonはpython.orgのmacOS版3.15.0をPYTHON_JIT=0と=1で比べた
ランタイム JITなし JITあり 中央値の比
Java(Temurin 25.0.4.1) 368〜387ms(-Xint) 16〜35ms(既定) 約21倍(373÷18)
Node.js v26.5.0 639〜673ms(--jitless) 67〜133ms(既定) 約10倍(649÷67)
Ruby 4.0.6 457〜526ms 95〜104ms(--yjit) 約4.9倍(501÷103)
LuaJIT 2.1 268〜287ms(-joff) 54〜71ms(既定) 約4.8倍(283÷59)
PHP 8.5.8 290〜481ms 152〜155ms(tracing) 約3.0倍(465÷154)
Python 3.15.0 763〜786ms 802〜886ms JITありが約5%遅い(771÷813)

今回の測定では、Javaは約21倍、V8は約10倍の差でした。ただし、この比較だけでは、型情報の利用やインライン展開など、個々の最適化が差にどれだけ寄与したかは分かりません。LuaJITの-joffはJITコンパイラだけを止め、アセンブラで書かれたインタプリタで動かすモードです(LuaJITの実行オプション)。PythonはJITを有効にした方が遅く、fibのような関数呼び出し中心の処理では3.15.0でもまだ効いていません。

中間の段も測りました。PHPのopcache.jit=functionは213〜232msでtracingより遅く、V8をSparkplugだけに絞る(--no-maglev --no-turbofan)と233〜338ms、Rubyの--zjitは743〜753msでインタプリタより遅い結果です。

倍率は負荷で数割動きます。2026年9月27日に同じ機械で測ったときは、Javaが約17倍、Node.jsが約15倍でした。fibは関数呼び出しと整数演算に偏った題材で、配列処理やオブジェクト操作、I/Oを含む処理の性能は評価していません。DBやネットワーク待ちが大半のWebアプリで、この倍率がそのまま出ることはありません。自分のコードで判断するときは、同じ入力でJITの有無を切り替え、複数回の中央値で比べてください。Javaのコードは次のとおりです(ほかの言語も同じ再帰で、同じく経過時間を測っています)。

public class Fib {
  static int fib(int n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }
  public static void main(String[] a) {
    long t = System.nanoTime();
    int r = fib(32);
    System.out.printf("%d %.0f ms%n", r, (System.nanoTime() - t) / 1e6);
  }
}

言語の外で動くJIT:データベース・正規表現・カーネル

同じ処理を大量に繰り返す場所なら、言語の処理系でなくても「実行時に専用の機械語を作る」価値があります。

PostgreSQLのJITとPostgreSQL 19での既定変更

PostgreSQLは、LLVMを組み込んでビルドした場合(--with-llvm)にJITを使えます。対象はWHERE句などの式評価と、ディスク上の行をメモリ上の表現に変換するタプルデフォーミングです(PostgreSQL 18 文書 30.1)。使うかどうかは問い合わせの推定コストで決まり、PostgreSQL 18の既定は次のとおりです(設定パラメータの文書)。

パラメータ 18の既定 意味
jit on JITを使ってよいか(19で既定off)
jit_above_cost 100000 このコストを超えたらJIT
jit_inline_above_cost 500000 関数・演算子のインライン展開
jit_optimize_above_cost 500000 重い最適化をかける

この判断は実行時でなく計画時に行われるため、プリペアドステートメントで汎用プランを使うと、準備した時点の設定が効きます。JITが使われたかはEXPLAIN ANALYZEの末尾にJIT:の欄(FunctionsやTiming: Generation … Emission … Total)が出るかで確認できます。

大きな変更が控えています。PostgreSQL 19のリリースノートは「JITを既定で無効に変更」と明記し、理由を「コストに基づく判定が信頼できないと判断された」としています。大きな分析クエリを多く流す環境では、手でjit = onに戻す必要があります。19は2026年10月15日にRC1が公開され、正式版は11月19日の予定です(RC1の告知)。

もう1つの落とし穴は、「onと表示されているのに動いていない」状態です。Homebrewで入れたPostgreSQL 18.6で確かめると、次のようになりました。

postgres=# SHOW jit;
 jit
-----
 on
postgres=# SELECT pg_jit_available();
 pg_jit_available
------------------
 f

pg_config --configureの出力に--with-llvmが無く、jit_above_costを0にしてもEXPLAIN ANALYZEにJIT:の欄は出ませんでした。公式文書も、LLVMなしでビルドされたサーバーでは条件を満たしてもJITは行われないと書いています。JITの効果を議論する前に、pg_jit_available()を1回実行してください。

正規表現エンジンとeBPFのJIT

正規表現もJITの典型的な使い道です。PCRE2はpcre2_jit_compile()で、コンパイル済みのパターンを機械語に変換でき、公式文書はこれを「パターンマッチを大きく高速化できる重い最適化」と位置づけています(pcre2jit)。PHPのpreg_*関数はPCRE2を使っており、pcre.jitの既定は1(有効)です(PCREの設定)。OPcacheのJITが無効でも、正規表現のJITは既定で動いています。

Linuxカーネルでは、パケットフィルタや監視に使うeBPFのプログラムがJITで機械語になります。net.core.bpf_jit_enableは0で無効、1で有効、2で有効かつカーネルログにトレースを出す設定です(カーネル文書 sysctl/net)。CONFIG_BPF_JIT_ALWAYS_ON付きでビルドされたカーネルでは1に固定され、ほかの値には変えられません。

JITが使えない・切るべき場面

JITは実行中に「書き込んだメモリを実行する」必要があるため、OSやセキュリティ設定、他の拡張との相性で止まることがあります。原因を知らないと「JITを有効にしたのに速くならない」で終わります。

iPhone・iPadにおけるJITの制限と例外

iOSとiPadOSでは、書き込み可能かつ実行可能なメモリを使えるのは、カーネルがApple専用の「dynamic code-signing」entitlementを確認できたアプリだけです(Apple Platform Security)。一般のアプリには付与されないので、実行時に機械語を作って動かせず、App Storeのエミュレーターやスクリプト言語の実行環境はインタプリタで動いています。

例外は、Appleが承認した代替ブラウザエンジンを使うアプリです。日本では専用ブラウザに加え、ブラウザエンジンスチュワードが提供するアプリ内ブラウズ用のアプリも対象で、iOS 26.2以降で認められています(EUはiOS 17.4以降)。Appleの案内とBrowserEngineKitの文書は、専用のentitlementを付けたうえでJIT用メモリの書き込みと実行を切り替える手順を定めています。検索でよく見る「iPhoneでJITを有効にする」は、開発者向けのデバッグ接続でこの制限を外す手法の話で、配布されたアプリが自前でJITを使える仕組みではありません。

コードカバレッジ・デバッガーとの併用

PHPでは、実行処理を差し替える拡張が入っているとJITが自動で無効になります。カバレッジ計測用のpcovを読み込んだ状態でopcache.jit=tracingを指定すると、次の警告が出てJITなしの速度のままでした。

Warning: JIT is incompatible with third party extensions that override zend_execute_ex(). JIT disabled.

Xdebugなども同じ仕組みで当たります。本番サーバーにデバッグ用の拡張を残したままJITを有効にしても効きません。opcache_get_status()のjit要素で、実際に有効になっているかを確認できます。

JITなしのビルドと、あえて切る設定

Pythonは前述のとおり、配布元のビルドにJITが入っていないことがあります。PostgreSQLもLLVMなしのビルドでは動きません。どちらも設定値だけ見ると有効に見えるので、sys._jit.is_available()やpg_jit_available()で確かめてから評価してください。

逆に、セキュリティのためにJITを切る設定もあります。Microsoft Edgeの強化セキュリティモードは、JavaScriptのJITを無効にしてメモリ関連の脆弱性を緩和すると説明しています。V8の--jitlessも実行可能メモリの確保をやめてインタプリタだけで動かすモードで、今回の計測では既定の約10倍遅くなりました。速度より攻撃面の縮小を優先する環境向けの設定です。

短命なプロセスのJIT負担とAOTの選択

短命なプロセスでは、JITのコンパイル負担を高速化の効果で回収できない場合があります。起動時間を含めて測り、AOT版と比べてください。JavaならGraalVMのNative ImageやJDK 25のAOTキャッシュ、.NETならNative AOTが候補です。ただし、JDKのAOTキャッシュはクラスのロード・リンク済みの状態(JEP 483)やメソッドのプロファイル(JEP 515)を再利用する仕組みで、Native Imageのようにアプリを事前に機械語化する機能ではありません。JITを前提にした処理系選びや性能設計から相談したい場合は、フルスクラッチ開発の支援も行っています。

よくある質問

JITとはどういう意味ですか?

「Just-In-Time(ちょうど間に合うように)」の略で、プログラミングではプログラムの実行中にコードを機械語へ変換するJITコンパイルを指します。読み方は「ジット」です。製造業のジャストインタイム生産方式も同じ略称ですが、別の概念です。

JITコンパイラとインタプリタの違いは何ですか?

インタプリタは命令を1つずつ解釈して実行し続けますが、JITはホットな部分を機械語に置き換えるので、繰り返し実行されるほど速くなります。多くのJIT搭載ランタイムは、起動直後はインタプリタで動き、温まった部分からJITに切り替える組み合わせで動いています。

JITとAOTはどちらが速いですか?

短時間で終わる処理ではAOT、長時間動くプロセスのピーク性能ではJITが有利になりやすいです。JITは実行時の型や分岐の傾向を使って最適化できますが、その分、温まるまでの時間とコンパイル用のメモリがかかります。

JavaのJITコンパイラとは何ですか?

HotSpot JVMに内蔵されたC1とC2の2つのコンパイラです。現行のJDKは階層型コンパイルが既定で、C1が素早くコンパイルしながらプロファイルを集め、特にホットな関数をC2が最適化します。長いループはOSRでループの途中から切り替わります。

PythonのJITコンパイラとは何ですか?

CPythonには3.13から実験的なJITがあり、3.14以降は公式のmacOS版・Windows版インストーラーに同梱されてPYTHON_JIT=1で有効になります。既定は無効で、配布元によってはJITなしでビルドされています。別実装のPyPyはトレーシングJITで動き、数値計算の関数単位ならNumbaも使えます。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

ほか 1 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.07 テックブログ ファインズの不正アクセスと153万件の予約者情報|予約システムを使う店舗の報告と通知の手順
  2. 2025.08.14 コラム トラクションとは?ビジネスでの意味・指標の目安とgain tractionの使い方
  3. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更・退会の手順
  4. 2026.10.19 テックブログ コミックシーモア不正アクセスで会員情報流出:再開時期・パスワード再設定と運営側のログ点検
  5. 2026.10.16 テックブログ バーガーキング情報漏洩どうしたらいい?約1,118万件の対象確認・補償・退会とメールの真偽

RELATED POSTS 関連記事

目次