Tiered Compilation in Java

Naveen AnamaneniUpdated · 5 min read

When a Java program starts, the JVM doesn't compile everything to machine code at once. It uses tiered compilation: code first runs in the interpreter, which starts instantly but is slow; methods that are called often are compiled quickly by the C1 compiler; and the hottest methods are recompiled with heavy optimizations by the C2 compiler. This gives fast startup and excellent peak speed. The basics of the JIT compiler were introduced in "JIT Compiler"; in this lesson, you will learn the tiers, see them in a real compilation log, and measure how much each one matters.

The Tiers

LevelRuns asProfilingPurpose
0InterpreterYesStart running immediately
1C1 code, no profilingNoSimple methods that won't benefit from C2
2C1 code, limited profilingSomeUsed when C2 is busy
3C1 code, full profilingYesCollect data (types, branches) for C2
4C2 code, fully optimizedNoPeak performance for hot code

A typical hot method goes 0 → 3 → 4: interpreted, then C1 with profiling, then C2 using the profile to optimize aggressively.

Interpreter, then quick C1 code, then optimized C2 code for hot methods

Seeing It Happen

This program calls a small method two million times:

Hot.java
public class Hot {
    static int square(int x) {
        return x * x;
    }
 
    public static void main(String[] args) {
        long sum = 0;
        for (int i = 0; i < 2_000_000; i++) {
            sum += square(i % 1000);
        }
        System.out.println(sum);
    }
}

With -XX:+PrintCompilation, the JVM logs each compilation. The lines for this program on JDK 25:

Terminal
java -XX:+PrintCompilation Hot
Terminal output
10    8       3       Hot::square (4 bytes)
10    9       4       Hot::square (4 bytes)
10    8       3       Hot::square (4 bytes)   made not entrant: not used
11   10 %     3       Hot::main @ 4 (36 bytes)
11   11       3       Hot::main (36 bytes)
11   12 %     4       Hot::main @ 4 (36 bytes)

Explanation: The columns are time in milliseconds, a compilation ID, a % for on-stack replacement (OSR), the tier, and the method. Within 10 milliseconds, square was compiled at tier 3 (C1) and then tier 4 (C2); the tier-3 version was then "made not entrant", meaning thrown away because the better version exists. main contains the long loop, so it was compiled while still running using OSR (%), first by C1, then by C2.

Example: How Much Each Tier Matters

The same program with a 200 million-iteration loop, run three ways and timed on the test machine:

Terminal
java -Xint Hot2
java -XX:TieredStopAtLevel=1 Hot2
java Hot2
ModeTime
-Xint (interpreter only)about 2,570 ms
-XX:TieredStopAtLevel=1 (C1 only)about 116 ms
Default (all tiers, C2 for hot code)about 102 ms

Explanation: Without any JIT compiler, the program is about 25 times slower. C1 alone recovers most of the speed for this simple loop; C2's extra optimizations add more. In bigger programs, the gap between C1 and C2 is usually larger.

Common Mistakes

Timing code the first time it runs. It's still interpreted or partly compiled.

Using -Xint or -XX:TieredStopAtLevel=1 in production for speed. They lower peak performance (C1-only can help short-lived tools start faster).

Reading "made not entrant" as an error. It's normal: an older compiled version was replaced.

Interview Corner

What is tiered compilation?

Tiered compilation is HotSpot's default strategy of combining the interpreter with two JIT compilers. Methods start interpreted (tier 0) while their invocation and loop counts are tracked; warm methods are compiled quickly by C1 with profiling (tier 3), and hot methods are recompiled by the optimizing C2 compiler (tier 4) using the collected profile. On-stack replacement lets long-running loops switch to compiled code mid-execution. This balances fast startup with high peak performance; wrong speculative assumptions trigger deoptimization back to the interpreter.

Practice

  1. Easy: Run any program with -XX:+PrintCompilation and find one method compiled at tier 4.
  2. Harder: Time a loop-heavy program with -Xint, -XX:TieredStopAtLevel=1 and the default, and explain the differences.

Quick check: Tiered compilation

4 questions
1Which tier is the interpreter?
2Which compiler produces tier 4 code?
3What does % mean in -XX:+PrintCompilation output?
4Why are programs slower at startup?

Key Takeaways

Was this tutorial helpful?

Lesson 448 of 482