javac turns each class into a .class file of bytecode; the JVM loads classes lazily, on first use, through a chain of class loaders.
Main.java → javac → Main.class (bytecode) → Class loader → Bytecode verifier → Interpreter / JIT → Native CPU
Bytecode
You can read the bytecode with javap -c:
$ cat Add.java
class Add { static int add(int a, int b) { return a + b; } }
$ javac Add.java && javap -c Add
static int add(int, int);
Code:
0: iload_0 // push local 0 (a)
1: iload_1 // push local 1 (b)
2: iadd // pop two ints, push their sum
3: ireturnClass loading: load → link → initialise
Loading
A class loader finds the bytes (from the module image, a JAR, the file system, the network…) and creates a
Classobject.Linking — verify
The bytecode verifier checks the code is type-safe and can't corrupt the stack or memory.
Linking — prepare
Static fields are allocated and set to default values (
0,null,false).Linking — resolve
Symbolic references (names of other classes, methods) become direct references (may be lazy).
Initialisation
Static initialisers and static field assignments run, top to bottom — exactly once, on first active use.
The class loader hierarchy
| Loader | Loads | Notes |
|---|---|---|
| Bootstrap | Core modules: java.base (java.lang, java.util…) | Written in native code; appears as null from Java |
| Platform (was Extension) | Other Java SE / JDK modules (java.sql…) | Parent is bootstrap |
| Application (System) | Your classes from the class path / module path | Parent is platform |
| Custom | Anything you want (plugins, hot reload, app servers) | Extend ClassLoader |
public class Main {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader()); // bootstrap → null
System.out.println(java.sql.Date.class.getClassLoader().getName());
ClassLoader app = ClassLoader.getSystemClassLoader();
System.out.println(app.getName() + " -> parent " + app.getParent().getName());
}
}null
platform
app -> parent platformLazy initialisation — classes load on first use
public class Main {
public static void main(String[] args) {
System.out.println("main starts");
System.out.println(Config.NAME); // compile-time constant: inlined, no init!
System.out.println(Config.port); // first active use → static block runs
System.out.println(Config.port); // already initialised
}
}
class Config {
static final String NAME = "app"; // constant — copied into the caller
static int port = 8080;
static { System.out.println("Config initialised"); }
}main starts
app
Config initialised
8080
8080What triggers class initialisation?
What is the parent delegation model and why does it exist?
ClassNotFoundException vs NoClassDefFoundError?