Just-in-Time Compilation Explainer
Just-in-time compilation, or JIT compilation, converts program code into native machine instructions while the program is running. Languages built around a virtual machine often begin with portable bytecode or another intermediate form that is easier to move between operating systems and processor families. An interpreter can execute that form immediately, one operation at a time, but repeated interpretation adds overhead. A JIT compiler watches execution and compiles selected regions into instructions the current CPU can run directly. The program therefore keeps portability at distribution time while gaining much of the speed of native code after it has warmed up.
A runtime does not normally compile everything at the highest optimization level as soon as a program starts. Compilation itself consumes processor time and memory, and some methods may run only once. Instead, counters and sampling profiles reveal “hot” methods and loops that account for a large share of execution. The runtime may first use a fast, lightly optimizing compiler, then recompile the busiest paths with more aggressive analysis. This tiered approach improves startup while reserving expensive optimization for code likely to repay the cost. A server application that runs for hours benefits differently from a short command that exits in a second.
Runtime profiles give a JIT information an ahead-of-time compiler may not possess. It can observe which branches are usually taken, which concrete object types appear at a call site, and how often a loop iterates. With that evidence, it can inline frequent calls, eliminate redundant checks, keep values in registers, and specialize general code for the common case. These decisions are often speculative rather than permanent. The generated machine code may contain guards that verify its assumptions. As long as reality matches the profile, the fast path continues without paying for every theoretical possibility.
If an assumption stops being true, the runtime can deoptimize. It maps the current machine state back to a valid interpreter or less-optimized frame, resumes execution safely, and may compile a more general version later. That ability lets the compiler make bold choices without changing the language’s meaning. It also explains why performance can shift during a run: code starts interpreted, becomes compiled, and may be replaced again as usage changes. Garbage collection, class loading, dynamic linking, and debugging all interact with generated code, so the runtime keeps metadata describing stack frames, references, and recovery points.
JIT compilation has costs. The compiler competes with the application for CPU time, generated code occupies memory, and early requests may arrive before important paths are optimized. Benchmarks can mislead if they measure only cold startup or, conversely, ignore warm-up entirely. Security systems also care about memory that transitions from writable code buffers to executable instructions. Mature runtimes manage these tradeoffs with compilation thresholds, code caches, background compiler threads, and rules that limit writable-and-executable memory. Some environments prohibit dynamic code generation, making interpretation or ahead-of-time compilation the practical alternative.
The main distinction is timing, not a promise that one strategy is always faster. Ahead-of-time compilation prepares native code before deployment and usually offers predictable startup. Interpretation offers immediate execution and flexibility with little compilation delay. A JIT sits between them, spending resources during execution to learn what the actual workload needs. Modern runtimes often combine all three techniques and may save profiles for later launches. The result is adaptive software: portable input code becomes processor-specific machine code, and that machine code can evolve as the running program reveals which operations truly matter. That ongoing feedback loop is why identical source code can settle into different machine-code shapes under different inputs, hardware features, and execution histories in real time.
A runtime counts calls and loop iterations or samples the program counter to find code that consumes significant execution time. Those frequently used regions are considered hot enough to justify compilation.
Thresholds vary by runtime and optimization tier. Code may first receive a quick compilation and later qualify for a slower, more aggressive pass after additional evidence accumulates.
Many optimizations depend on observed assumptions, such as one object type dominating a call site. Generated code includes guards that confirm those assumptions while it runs.
When a guard fails, the runtime can deoptimize to a safe interpreter or less-specialized version, then compile again using the new profile. Program behavior stays correct even though the implementation changes.
Ahead-of-time compilation produces native code before the program is launched, which favors predictable startup and deployment. JIT compilation produces selected native code during execution.
Because it sees live behavior, a JIT can specialize for the current workload and processor. That advantage is balanced by warm-up time, compiler CPU use, and a larger runtime system.
Explore more "Explainers"
Discover additional explainers across politics, science, business, technology, and other fields. Each explainer breaks down a complex idea into clear, everyday language—helping you better understand how major concepts, systems, and debates shape the world around us.
