Java learning pathSection 6

Execution Engine

Understand interpretation, HotSpot tiered compilation, profiling, the native code cache, hot methods and loops, and correctness-preserving deoptimization.

1

Interpreter

The execution engine executes the bytecode produced by javac. Its interpreter can begin executing bytecode instructions without first optimizing an entire method into native machine code, making it useful for startup and cold code.

For int sum = a + b, the bytecode describes loading operands, adding, and storing the result. Interpretation performs the operations described by those instructions each time the code executes. Calling this translation instruction by instruction is a teaching simplification; an interpreter is itself native code implementing bytecode semantics.

The trade-off is repeated interpretation overhead. Paying a large optimizing-compilation cost for every rarely used method would also be wasteful, so interpretation remains valuable even in a JIT-enabled runtime.

Bytecode starts in the interpreter, profiling selects compilation, native code is stored in the code cache, and invalid assumptions lead back through deoptimization
Adaptive execution balances quick startup with optimized execution of code that becomes hot.
2

Just-In-Time (JIT) Compiler

The JIT compiles selected bytecode into native machine code while the program is running. Subsequent executions can reuse compiled code rather than repeatedly interpreting the same instructions.

The source's million-iteration sum loop illustrates why frequently executed code is worth compiling. Compilation itself consumes CPU and memory; a hot loop is a candidate, not a promise that a particular iteration count always triggers a particular compiler.

Runtime information can enable optimizations unavailable from source alone, including specialization for observed receiver types and branch behavior. Those optimizations must still preserve Java semantics.

Example
long sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;
}
System.out.println(sum); // 499999500000
// A hot-loop candidate, not a guaranteed compilation threshold.
3

HotSpot JVM and Adaptive Optimization

HotSpot is the JVM implementation used by OpenJDK and many distributions, including Oracle JDK. Its name reflects the strategy of finding execution hot spots and spending optimization effort there.

It combines interpretation, runtime profiling, JIT compilation, and adaptive optimization. A method such as calculateSalary() that is invoked repeatedly may justify native compilation, while a one-time initialization path may remain interpreted.

These are HotSpot implementation techniques, not requirements that every conforming JVM use C1, C2, or the same optimization policy. Configuration and workload influence the execution path.

4

Tiered Compilation: Startup versus Peak Performance

Tiered compilation combines interpretation with fast and more aggressive compilation. C1 generally produces code quickly and can retain profiling instrumentation; C2 spends more effort optimizing code expected to run enough to repay that cost.

The source shows interpreter, C1, more runtime data, and C2 as a conceptual progression. HotSpot does not require every method to visit every level in that order. Queue pressure, profiling maturity, runtime flags, and method characteristics can change the chosen route.

Warmup is therefore real: the code mix, profiles, and compiled versions can evolve during a run. Comparing a first request to a warmed steady-state request can measure different execution regimes.

Tiered Compilation: Startup versus Peak Performance
Typical HotSpot levelExecution strategyPurpose
0InterpreterBegin execution and gather initial information
1C1 without profilingQuick compiled execution when profiling is not needed
2C1 with limited profilingCompile quickly with selected counters
3C1 with fuller profilingCollect richer data for further optimization
4C2 optimized codeUse mature profiles for peak execution performance
5

Code Cache

The code cache holds executable native code generated by the JVM, including compiled methods and runtime code. It is distinct from the Java object heap: increasing the heap is not the same as increasing available compiled-code storage.

Caching avoids compilation on every invocation. The source's compile-once/reuse-many-times model explains the benefit, but an individual method can have several compiled versions over time as profiling and optimization decisions change.

Compiled code may become invalid, be superseded, or be reclaimed. Code-cache capacity and compilation activity are runtime concerns; an indefinitely growing cache of every version would not be sustainable.

Code Cache
StorageContainsNot the same as
Class representationBytecode and class/method metadataA JIT-compiled method body
Code cacheExecutable generated native codeThe Java heap containing domain objects
Java heapObjects and arraysAll memory used by the JVM process
6

Profiling and Compilation Decisions

The engine gathers execution information to decide where compilation is worthwhile. Invocation counters and loop back-edge counts identify repeated activity; receiver-type and branch profiles can guide specialization and layout.

The document also lists execution time and memory usage as useful runtime observations. Do not interpret that list as a guarantee that every invocation is timed or every allocation measured by the ordinary JIT profile; diagnostic profilers and compiler instrumentation have different scopes.

A loop that calls calculate() a million times can make both the enclosing loop and calculate() interesting. Inlining may then remove a standalone call in the optimized caller, so source-level method counts do not map one-to-one to final machine-code calls.

  • Invocation frequency: is a method executed often enough to justify compilation?
  • Loop activity: is an already-running method spending significant time on a back edge?
  • Receiver types: can a virtual call be specialized safely?
  • Branch behavior: is a path common enough to optimize for?
  • Compilation cost: does expected reuse justify additional compiler effort?
7

Hot Methods

A hot method is invoked frequently. The JVM can schedule it for compilation, install the resulting native entry point, and reuse that code for later calls.

The source's add() sketch is intended to return a + b; its illustrative void declaration is corrected below to int. Frequency alone does not fix the final optimization: a small helper may be inlined into its caller instead of remaining a separate call.

Example
static int add(int a, int b) {
    return a + b;
}

long total = 0;
for (int i = 0; i < 1_000_000; i++) {
    total += add(i, 1);
}
System.out.println(total);
8

Hot Loops and On-Stack Replacement

A method need not be called many times to contain hot code. A single invocation with millions of loop iterations can produce a large loop back-edge count and become an optimization candidate.

The JIT may use loop unrolling, invariant-code motion, constant folding, strength reduction, and dead-code elimination when legal and profitable. These transformations reduce overhead or redundant work rather than changing the result the Java program is entitled to observe.

On-stack replacement can transfer the state of an already executing interpreted loop into compiled code at a supported entry point. It avoids waiting for the method to return or restarting the loop from iteration zero.

Example
long sum = 0;
for (int i = 0; i < 10_000_000; i++) {
    sum += i;
}
System.out.println(sum);
// One invocation can become hot through repeated loop iterations.
9

Deoptimization Preserves Correctness

Optimized code may depend on runtime assumptions. If shape.draw() has only seen Circle receivers, the JIT may generate a guarded specialized path or inline Circle.draw(). A later Rectangle receiver requires correct dispatch, not continued use of an invalid Circle-only assumption.

A guard can take another valid path, or invalidated assumptions can cause deoptimization. The VM can reconstruct interpreter-visible execution state, stop using invalid compiled code, gather new profiles, and compile a new version if appropriate.

Deoptimization is the safety mechanism that allows speculation without changing Java behavior. Not every new receiver necessarily triggers it: generated code may already support the new receiver or have a fallback path.

Example
interface Shape { void draw(); }
class Circle implements Shape {
    public void draw() { System.out.println("circle"); }
}
class Rectangle implements Shape {
    public void draw() { System.out.println("rectangle"); }
}
// If a hot call site previously saw only Circle, a Rectangle must
// still dispatch correctly, via a valid path or deoptimization.
10

Execution Flow and Quick Revision

The complete source flow is adaptive: bytecode begins executing, profiling identifies hot methods or loops, compilation produces native code, the code cache enables reuse, and invalid assumptions can send execution back through deoptimization and recompilation.

Interpretation favors getting started; optimized compilation favors repeated execution. A healthy performance analysis distinguishes startup, warmup, compilation cost, code-cache use, and steady-state execution instead of assuming every line is immediately native code.

Execution Flow and Quick Revision
TermRoleCaveat
InterpreterExecutes bytecode without optimizing compilation firstUseful for cold code as well as startup
JITProduces reusable native codeConsumes compilation CPU and memory
HotSpotAdapts execution using runtime observationsOne JVM implementation, not the Java specification
Tiered compilationBalances fast compilation and deeper optimizationNot every method visits every level
Code cacheStores generated executable codeNative storage, separate from object heap
ProfilingGuides hotness and specialization decisionsDifferent from a full diagnostic profiler
Hot loopRepeated work within a methodCan benefit from OSR during one invocation
DeoptimizationRecovers when assumptions are invalidPreserves semantics while enabling speculation