How Java Code Runs: Bytecode, Class Loader and JIT
Introduction
When you type
(Note - This tutorial builds on JDK vs JRE vs JVMClick to read tutorial: JDK vs JRE vs JVM - Differences Explained with Examples, which introduces the parts of the JVM.) From source code to running code A Java program goes through these stages -
OUTPUT of the above code
Looking at the bytecode
The JDK's
OUTPUT of the above command
Explanation of the bytecode
OUTPUT of the above code
Explanation of the code
Classes are loaded only when needed
The JVM does not load every class at startup. It loads and initializes a class the first time the program actually uses it. A static initializer block - code in
OUTPUT of the above code
The
The program below times the same work in ten rounds. Watch how much faster the later rounds are, once the JIT compiler has compiled
OUTPUT of the above code (timings vary from run to run)
Explanation of the code
java MyProgram, a lot happens before your first line of code runs - and while it runs. This tutorial gives an overview of how Java code runs: what bytecode is, how the class loader finds and loads classes, and how the JIT compiler makes Java programs fast. We will start with the journey of a program from source code to running code. We will then look inside a .class file at its bytecode. Next we will see the class loaders at work, and when exactly a class is loaded and initialized. Lastly, we will watch the JIT compiler speed up a method while a program runs.(Note - This tutorial builds on JDK vs JRE vs JVMClick to read tutorial: JDK vs JRE vs JVM - Differences Explained with Examples, which introduces the parts of the JVM.) From source code to running code A Java program goes through these stages -
- Compile -
javacchecks the source code and translates each class into bytecode, saved in a.classfile. Bytecode is a compact set of instructions for the JVM, not for any real processor. - Load - when the program runs, the JVM's class loaders find the
.classfiles the program needs and read them into memory, one class at a time, as they are needed. - Link - the JVM verifies that the bytecode is valid and safe, prepares memory for the class's static variables, and resolves references to other classes.
- Initialize - the JVM runs the class's static initialization code, giving static variables their starting values.
- Execute - the interpreter starts running the bytecode instruction by instruction. Methods which run often are compiled to native machine code by the JIT compiler, and from then on run at full speed. Meanwhile the garbage collector frees the memory of objects which are no longer used.
The program used in this tutorial
//Calculator.java
public class Calculator {
static int sumUpTo(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) {
System.out.println("Sum of 1 to 100 = " + sumUpTo(100));
}
}
Sum of 1 to 100 = 5050
javap tool disassembles a .class file. Let us look at the bytecode of Calculator, and in particular its sumUpTo() method -
javap -c Calculator
Compiled from "Calculator.java"
public class Calculator {
public Calculator();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
static int sumUpTo(int);
Code:
0: iconst_0
1: istore_1
2: iconst_1
3: istore_2
4: iload_2
5: iload_0
6: if_icmpgt 19
9: iload_1
10: iload_2
11: iadd
12: istore_1
13: iinc 2, 1
16: goto 4
19: iload_1
20: ireturn
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 100
5: invokestatic #13 // Method sumUpTo:(I)I
8: invokedynamic #19, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
13: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: return
}
- The JVM is a stack machine. Instructions push values onto an operand stack, and other instructions take them off to work with them.
iconst_0pushes the int value 0, andistore_1stores it in local variable 1 (sum). - The
forloop becomes a comparison and jumps -if_icmpgt 19jumps out of the loop to instruction 19 wheniis greater thann, andgoto 4jumps back to the start of the loop. iaddadds two ints,iinc 2, 1adds 1 to local variable 2 (i++), andireturnreturns an int from the method.- The same bytecode runs on Windows, macOS and Linux. It is the JVM on each machine which turns it into instructions for the real processor.
- Bootstrap class loader: Built into the JVM. Loads the core classes of Java, such as
java.lang.Stringandjava.lang.System. - Platform class loader: Loads the other standard library modules, such as
java.sql. - Application (system) class loader: Loads your own classes, and the libraries on the classpath.
java.lang.String with its own version. Every class can tell us which class loader loaded it -
Finding the class loader of a class
//WhoLoadedMe.java
public class WhoLoadedMe {
public static void main(String[] args) {
System.out.println("WhoLoadedMe : " + WhoLoadedMe.class.getClassLoader().getName());
System.out.println("java.sql.Date : " + java.sql.Date.class.getClassLoader().getName());
System.out.println("java.lang.String: " + String.class.getClassLoader());
}
}
WhoLoadedMe : app java.sql.Date : platform java.lang.String: null
WhoLoadedMe, our own class, was loaded by theapp(application) class loader, andjava.sql.Dateby theplatformclass loader.String's class loader is printed asnull. The bootstrap class loader is part of the JVM itself, not a Java object, so Java represents it asnull.
static { ... } - runs when its class is initialized, so it shows us exactly when that happens.
Showing when a class is initialized
//LazyLoading.java
public class LazyLoading {
static class Helper {
static {
System.out.println(" -> Helper class initialized");
}
static void help() {
System.out.println(" -> Helper.help() called");
}
}
public static void main(String[] args) {
System.out.println("main() started");
System.out.println("About to use Helper for the first time");
Helper.help();
System.out.println("Using Helper again");
Helper.help();
}
}
main() started About to use Helper for the first time -> Helper class initialized -> Helper.help() called Using Helper again -> Helper.help() called
Helper class was initialized only when Helper.help() was first called - after main() had started - and only once. Loading classes on demand keeps startup fast, as a program only pays for the classes it uses.
The interpreter and the JIT compiler
The JVM starts by interpreting bytecode, which lets a program start running immediately. Interpreting is slower than running native code, however, so the JVM counts how often each method runs. When a method becomes hot, the Just-In-Time (JIT) compiler compiles it to optimized native machine code, while the program keeps running. HotSpot, the standard JVM, has two JIT compilers - a quick one (C1) used first, and a highly optimizing one (C2) for the hottest code.The program below times the same work in ten rounds. Watch how much faster the later rounds are, once the JIT compiler has compiled
sumUpTo().
Watching the JIT compiler speed up a method
//JitWarmup.java
public class JitWarmup {
static long sumUpTo(int n) {
long sum = 0;
for (int i = 1; i <= n; i++) {
sum += i % 7;
}
return sum;
}
public static void main(String[] args) {
for (int round = 1; round <= 10; round++) {
long start = System.nanoTime();
long result = 0;
for (int k = 0; k < 200; k++) {
result += sumUpTo(10_000);
}
long micros = (System.nanoTime() - start) / 1_000;
System.out.println("Round " + round + ": " + micros + " microseconds (result " + result + ")");
}
}
}
Round 1: 8668 microseconds (result 5999600) Round 2: 1683 microseconds (result 5999600) Round 3: 1651 microseconds (result 5999600) Round 4: 1557 microseconds (result 5999600) Round 5: 1555 microseconds (result 5999600) Round 6: 1559 microseconds (result 5999600) Round 7: 1557 microseconds (result 5999600) Round 8: 1557 microseconds (result 5999600) Round 9: 1557 microseconds (result 5999600) Round 10: 1557 microseconds (result 5999600)
- Each round calls
sumUpTo(10_000)200 times and measures the time taken withSystem.nanoTime(). - The first round runs mostly in the interpreter, so it is the slowest. As
sumUpTo()gets hot, the JIT compiler compiles it, first with C1 and then with C2, and later rounds run many times faster. - This is why Java programs "warm up", and why you should never judge Java's performance by timing code just once.
javap to look at bytecode, and saw that the JVM is a stack machine. Next we met the bootstrap, platform and application class loaders and the parent delegation model, and saw that classes are loaded and initialized only when first used. Lastly, we saw how the interpreter and the JIT compiler work together, and watched a method get much faster as the JIT compiled it. In the next tutorial, we will try out Java code interactively with JShellClick to read tutorial: JShell Tutorial: Java's Interactive REPL with Examples.Java Fundamentals Tutorials
Compiling and Running Java Programs with javac and javaClick to read tutorial: Compiling and Running Java Programs with javac and java JShell Tutorial: Java's Interactive REPL with ExamplesClick to read tutorial: JShell Tutorial: Java's Interactive REPL with Examples What is Java? History, Features, Editions and UsesClick to read tutorial: What is Java? History, Features, Editions and Uses JDK vs JRE vs JVM - Differences Explained with ExamplesClick to read tutorial: JDK vs JRE vs JVM - Differences Explained with Examples How to Install the JDK and Set JAVA_HOME and PATHClick to read tutorial: How to Install the JDK and Set JAVA_HOME and PATH Choosing a Java IDE: IntelliJ IDEA vs Eclipse vs VS CodeClick to read tutorial: Choosing a Java IDE: IntelliJ IDEA vs Eclipse vs VS Code Java Hello World Program Explained Line by LineClick to read tutorial: Java Hello World Program Explained Line by Line
Compiling and Running Java Programs with javac and javaClick to read tutorial: Compiling and Running Java Programs with javac and java JShell Tutorial: Java's Interactive REPL with ExamplesClick to read tutorial: JShell Tutorial: Java's Interactive REPL with Examples What is Java? History, Features, Editions and UsesClick to read tutorial: What is Java? History, Features, Editions and Uses JDK vs JRE vs JVM - Differences Explained with ExamplesClick to read tutorial: JDK vs JRE vs JVM - Differences Explained with Examples How to Install the JDK and Set JAVA_HOME and PATHClick to read tutorial: How to Install the JDK and Set JAVA_HOME and PATH Choosing a Java IDE: IntelliJ IDEA vs Eclipse vs VS CodeClick to read tutorial: Choosing a Java IDE: IntelliJ IDEA vs Eclipse vs VS Code Java Hello World Program Explained Line by LineClick to read tutorial: Java Hello World Program Explained Line by Line