Learnitweb

Understanding JVM Tiered Compilation

1. JVM Has Two JIT Compilers: C1 and C2

Modern JVMs include two different compilers, each with a specific purpose:

C1 (Client Compiler)

  • Performs quick, lightweight, early-stage optimizations
  • Used for tiers 1, 2, and 3
  • Prioritizes fast compilation over deep optimization

C2 (Server Compiler)

  • Performs aggressive, complex, high-cost optimizations
  • Used only at tier 4
  • Produces the fastest possible machine code, but takes longer to compile

This structure is known as tiered compilation, and it dramatically improves how Java warms up and stabilizes performance.

2. Understanding Compilation Levels (Tiers)

Every compiled method receives a compilation tier level from 0 to 4:

TierCompilerMeaning
0NoneInterpreted only
1C1Simple compilation
2C1Limited profiling and optimization
3C1Full optimization with profiling
4C2Highest optimization level

What this means:

  • A method starting at Tier 1 → JVM suspects it may be hot
  • As it runs more often → JVM recompiles it at higher tiers
  • When very hot → it reaches Tier 4 and is compiled by C2

Once at Tier 4:

The method becomes fully optimized machine code.

3. How the JVM Chooses a Compilation Level

The JVM constantly profiles the code while it runs. It watches for:

  • How many times a method is called
  • How many loop iterations it performs
  • How complex the method is
  • How expensive it is to compile
  • Whether it is worth compiling again at a higher tier

This profiling helps the JVM decide:

  • Should we compile this method?
  • To what tier?
  • Should we recompile it later with deeper optimizations?
  • Is it worth storing this method in the code cache?

Hot methods move to higher tiers

Critical methods eventually reach Tier 4

Rarely used methods remain interpreted

4. Textual Diagram: JVM Tiered Compilation Workflow

Below is a conceptual diagram showing how a method moves through compilation tiers:

                   +----------------------+
                   |  Method Interpreted  |  (Tier 0)
                   +----------+-----------+
                              |
                     JVM Profiling Engine
                              |
                              v
                   +----------------------+
                   |   C1 Compilation     |  (Tier 1)
                   +----------+-----------+
                              |
                    More Profiling Data
                              |
                              v
                   +----------------------+
                   |   C1 Optimization    |  (Tier 2 & 3)
                   +----------+-----------+
                              |
                       Very Hot Method
                              |
                              v
                   +----------------------+
                   |   C2 Compilation     |  (Tier 4)
                   | (Highest Optimization)
                   +----------------------+
                              |
                   Storing into Code Cache

5. Example From the Transcript: isPrime Method

In your earlier prime-number example, the JVM:

  1. Started interpreting the isPrime() method
  2. Later compiled it at Tier 3 using C1
  3. After more executions, decided it was truly hot
  4. Recompiled it using C2Tier 4
  5. Stored the optimized version in the code cache

This happens because:

  • isPrime() is called thousands of times
  • It contains loops and arithmetic operations
  • Optimizing it provides massive performance benefit

This is typical JVM JIT behavior.

6. What Is the Code Cache?

The code cache is a special region of JVM memory where JIT-compiled machine code is stored.

When a method is placed in the code cache:

  • It executes as fast as a native C program
  • JVM no longer interprets its bytecode
  • JVM does not need to recompile it again unless profiling instructs otherwise

Methods marked with % in the -XX:+PrintCompilation output are stored in the code cache.

7. Seeing Compilation Activity Using -XX:+PrintCompilation

Earlier we used this command:

java -XX:+PrintCompilation Main 5000

This prints lines like:

95   3       PrimeNumbers::isPrime (39 bytes)
210  8 %     PrimeNumbers::isPrime (39 bytes)

This tells us:

  • The method was first compiled (level 3)
  • Later recompiled at level 4 (% indicates code cache placement)

But what if you want more detailed output, saved to a file?

8. Logging Compilation to a File Using -XX:+LogCompilation

The JVM offers a more detailed flag:

But it must be unlocked first.

Step 1 — Enable diagnostic options:

-XX:+UnlockDiagnosticVMOptions

Step 2 — Enable compilation logging:

-XX:+LogCompilation

Step 3 — Run the program:

java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation Main 5000

This generates a file in your working directory:

hotspot_pidXXXXX.log

or sometimes:

hotspot.log

9. Inspecting the Log File

Open the log file in a text editor.

Search for:

isPrime

You will find entries like:

<task compile_id='42' method='PrimeNumbers isPrime' level='3' ...>

Later:

<task compile_id='77' method='PrimeNumbers isPrime' level='4' ...>

And inside these tasks you will see:

  • Which compiler was used (C1, C2)
  • Why it was compiled
  • The size of the method
  • Whether it was inlined
  • Bytecode analysis
  • Machine code generation steps

Key observation:

  • First, a tier 3 C1 compilation
  • Later, a tier 4 C2 compilation

This matches what we saw using PrintCompilation, but in much greater detail.

10. When Should You Use LogCompilation?

Use it when:

  • You want a deeper understanding of JVM performance
  • You’re analyzing a remote server where console access is limited
  • You need to debug warm-up behavior
  • You’re doing advanced benchmarking
  • You want to know why certain methods become hot

It is especially helpful in:

  • Low-latency applications
  • High-performance computing
  • JVM tuning
  • Machine learning workloads
  • Performance-sensitive microservices