Just-In-Time (JIT) compilation is a dynamic translation technique used by many modern language runtimes to convert intermediate representations—commonly bytecode or other portable instruction sets—into native machine code while a program is running. Rather than relying solely on ahead-of-time (AOT) compilation or per-instruction interpretation, a JIT attempts to combine the portability of intermediate code with the speed of native execution.

How JIT works

A typical JIT-enabled runtime begins by executing code using an interpreter or a simple baseline compiler. As the program runs, the system identifies "hot" code paths—functions or loops that are executed frequently. When a region becomes hot, the JIT translates that region into optimized native code and replaces the interpreted or baseline representation. This process often involves:

  • Profiling to detect frequently executed code and collect runtime information (types, branch frequencies, inlining opportunities).
  • Code generation that emits machine instructions tailored to the observed usage patterns.
  • Optimization steps such as inlining, constant folding, loop unrolling, and specialized calling conventions.
  • Fallback and deoptimization: if assumptions made by the optimizer become invalid, the runtime can revert to a safe representation.

Strategies and tiers

Modern systems often use a tiered approach. A lightweight interpreter or a fast baseline compiler handles initial execution to reduce startup delay. When profiling indicates stable hot paths, a higher-tier optimizing JIT compiles those paths with more aggressive transformations. This design balances the immediate responsiveness of interpretation with the high throughput of optimized native code.

Advantages and trade-offs

JIT compilation can deliver significant run-time performance gains because it uses actual run-time data to produce specialized code. It permits aggressive optimizations that are unsafe or impossible without runtime information. The trade-offs include additional memory to hold generated native code, CPU work spent compiling during execution, and an initial warm-up period before peak performance is reached. Runtimes mitigate these costs using on-demand compilation, incremental compilation, and compilation caches.

History, examples and relation to interpretation

The concept of translating code at run-time has existed in various forms for decades as an alternative to purely interpreted execution and ahead-of-time compilation. Many widely used platforms implement JIT techniques, for example the Java Virtual Machine, the V8 JavaScript engine, the .NET Common Language Runtime, PyPy for Python, and LuaJIT. For contrast, an interpreter processes each intermediate instruction each time it runs, while a JIT emits native code so the translation work need not be repeated.

Typical use cases and notable facts

  • High-performance server applications and long-running processes benefit most, because they amortize compilation overhead over time.
  • Dynamic languages gain large wins from JITs because type and control-flow information discovered at runtime enable specialization.
  • Tooling around JITs includes profiling interfaces, garbage collection integration, and safepoints to coordinate execution and compilation.

For further technical perspective and implementation details, see a general JIT overview: JIT overview. Understanding JITs helps explain why some programs appear slower at startup but faster after running for a while: the runtime is translating and optimizing hot code to native instructions for subsequent use.