JVM Internals & Memory

JIT Compilation

HotSpot interprets first, then tiers up: C1 compiles quickly with light optimization, C2 recompiles the hottest methods aggressively using profile data — inlining, devirtualizing, escape-analyzing, and deoptimizing when its speculations break.
  • Tiered: interpreter → C1 (profiled) → C2 at ~10k invocations; loops trigger on-stack replacement
  • Inlining is the gateway optimization — it unlocks everything else
  • Speculation + deoptimization: monomorphic call sites devirtualize; a new receiver type deopts back
  • Escape analysis: non-escaping objects become stack scalars — allocation eliminated
  • Warm-up is real: benchmark and load-test only after the JIT settles (Microbenchmarking)
  • Code cache full = compilation silently stops — monitor it

Why late compilation wins (Optimizing Java ch. 10): by the time C2 compiles a method it has thousands of profile samples — which branch is 99% taken, which instanceof always passes, that a virtual call always lands in one implementation. It compiles the observed program, guards the assumptions cheaply, and deoptimizes (falls back to the interpreter, recompiles) if reality changes. Static compilers must be correct for all inputs; the JIT only for the ones that happen, and it gets to change its mind.

Watching the JIT work
$ java -XX:+PrintCompilation App | grep MyService
  312   45   3   com.acme.MyService::price (86 bytes)        // tier 3 (C1+profile)
  899  102   4   com.acme.MyService::price (86 bytes)        // tier 4 (C2)
  903   45   3   com.acme.MyService::price (86 bytes) made not entrant  // replaced
// deeper: -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining, or JITWatch on -XX:+LogCompilation

Inlining replaces a call with the callee's body — killing call overhead and, critically, letting optimizations see across method boundaries: constants propagate, allocations become scalars (escape analysis), locks on non-escaping objects vanish (lock elision), bounds checks hoist out of loops, and loops vectorize. Small methods inline best (-XX:MaxInlineSize ~35 bytecodes, hot methods up to FreqInlineSize ~325) — one more reason the "many small methods" style is fast, not slow, on the JVM.

Sources
  • Optimizing JavaCh. 9–10 — Code Execution; Understanding JIT Compilation
  • Optimizing Cloud Native Java (2nd ed.)Ch. 6 — Code Execution on the JVM