JVM & Memory

100. JIT Tiered Compilation and Escape Analysis

HotSpot executes bytecode through an interpreter and tiered JIT compilers (C1 client compiler for rapid startup, C2 server compiler for aggressive peak optimization), leveraging runtime profiling and escape analysis.

Java 21 (HotSpot C1 & C2 compilers)

Explanation

Java combines interpretation with Just-In-Time (JIT) compilation in a system called Tiered Compilation (-XX:+TieredCompilation, default since Java 7). When a program starts: - Tier 0: Pure bytecode interpretation. Fast startup, collects execution profiling metrics (method invocation counters, loop backedge counters). - Tiers 1โ€“3 (C1 Client Compiler): Compiles frequently called methods into native machine code with varying degrees of profiling instrumentation. Produces quick native code without spending too much time optimizing. - Tier 4 (C2 Server Compiler / Graal): When a method becomes "hot", C2 analyzes the profiling data to perform aggressive optimizations: loop unrolling, speculative devirtualization of polymorphic method calls, dead code elimination, and method inlining.

A key optimization is Escape Analysis (-XX:+DoEscapeAnalysis). C2 examines whether an object allocated inside a method escapes the method's scope (e.g., returned to a caller, stored in a field, or passed to a non-inlined method): 1. GlobalEscape: Escapes the thread and method. Must be allocated on the heap. 2. ArgEscape: Escapes the method as an argument, but does not escape the current thread. Lock synchronization can be eliminated (Lock Elision). 3. NoEscape: Never leaves the method. The JVM can perform Scalar Replacement โ€” replacing the heap object allocation entirely with primitive local registers, resulting in zero GC overhead!

Example

public class EscapeAnalysisDemo {
    record Point(int x, int y) {}

    // With Escape Analysis and Scalar Replacement, 'p' does NOT escape!
    // The JIT allocates x and y directly into CPU registers, bypassing the heap.
    public static int computeDistance(int a, int b) {
        Point p = new Point(a, b); // NoEscape -> scalar replaced!
        return p.x() * p.x() + p.y() * p.y();
    }

    public static void main(String[] args) {
        long start = System.nanoTime();
        int sum = 0;
        // Warm up loop to trigger C2 JIT compilation
        for (int i = 0; i < 10_000_000; i++) {
            sum += computeDistance(i, i + 1);
        }
        long duration = (System.nanoTime() - start) / 1_000_000;
        System.out.println("Result: " + sum + " in " + duration + " ms");
        // Inspect JIT behavior with flags:
        // -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
    }
}

Sources