Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Java › JVM

JIT warmup

Why are the first requests right after a deployment slow?

Java code is compiled to bytecode, and the JVM runs it by interpreting it one bytecode instruction at a time. The JVM finds the methods that run often and compiles them again, this time to machine code (JIT), and from then on they run much faster.

Checked against real execution · Values actually measured on JDK 21

Concepts in this lab

JIT compiler
This JVM component compiles frequently executed bytecode to machine code while the program runs, and from then on runs that machine code directly. At first the interpreter interprets and runs the bytecode one instruction at a time; then the JVM finds the parts that run repeatedly and optimizes them. This is why a Java application is faster after it has run for a while than it is right after startup (warmup).
Bytecode
This is the intermediate form of code that the Java compiler (javac) produces from source code, and it is stored in .class files. It is not the machine code of a particular CPU or operating system but a set of instructions that the JVM understands, so it runs on the JVM of any platform.
Java Virtual Machine (JVM)
This is the program that reads and runs Java bytecode. Each operating system has its own version of it, so a Java program that is compiled once can run on several operating systems without modification. It loads classes, manages memory (garbage collection), and converts bytecode to machine code and runs it.
Class loader
This JVM component finds class files and reads them into memory. Classes are not all read when the program starts; each class is loaded at the point when it is first needed. After a class is loaded, the JVM makes it ready for use through linking (verification, preparation, and resolution of symbolic references) and initialization (running static initializers).

Try these

Running with the default settings

  1. The Java compiler (javac) turns source code into bytecode, not machine code. When the program starts, the interpreter of the JVM interprets and runs this bytecode one instruction at a time. The bytecode runs on any CPU, but this is much slower than running machine code directly.
  2. Batch #1: 5.8ms. At first the code is run by the interpreter. The JVM counts how many times the method has been called and how many times the loop has run, and when the counts pass a threshold, it compiles this method to machine code. This batch ran as interpreted execution until the compiled code was ready (the compilation itself runs on another thread).
  3. Batch #2: 1.7ms. The code has started to run as compiled machine code. It is 3 to 4 times faster than the first batch. Using the information it gathered while running (such as which branches are taken often), the JVM optimizes the code once more, this time more heavily.
  4. Batch #3: 1.4ms.
  5. Batch #4: 1.7ms.
  6. Batch #5: 1.5ms.
  7. Batch #6: 1.4ms.
  8. Batch #7: 1.5ms.
  9. Batch #8: 1.4ms.
  10. Batch #9: 1.4ms.
  11. Batch #10: 1.4ms.
  12. Batch #11: 1.6ms.
  13. Batch #12: 1.7ms.

Result of this run

Code

static long work(int n) {                  // the method that was measured
    long s = 0;
    for (int i = 0; i < n; i++) s += (i * 31L) ^ (s >>> 3);
    return s;
}

for (int batch = 1; batch <= 12; batch++) {
    long t = System.nanoTime();
    for (int k = 0; k < 2000; k++) work(1000);          // one batch = 2,000 calls
    print((System.nanoTime() - t) / 1000 + " microseconds");
}

// Run: java JitWarmup.java

Assumptions of this model

The numbers are values measured by actually running the code above on JDK 21 on this PC (the first of two runs with the default settings, and one run with -Xint). The values of "Warmed up in advance" are the default-settings measurement with the first batch removed and the rest moved up, and the last value (1.4ms) is not a measurement but a similar value added at the end. The numbers vary with the CPU and with when the code is run, but the shape is the same: the first period is slow, and the code gets faster after that. A JVM with the default settings applies a light compilation (C1) and a strongly optimizing compilation (C2) in stages.

Previous lab: GC and memory leaks · Next lab: Class loading and initialization

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these