class loading
load → link → initialize. and the exact moments a class gets initialized.
- chapter
- chapter 13.05
- reading time
- 8 min read
every class goes through three phases. loading: find the bytes and turn them into a class. linking: verify the bytecode, give static fields their zero values, and resolve names into real pointers (lazily). initialization: run <clinit> (your static fields and static blocks), exactly once, at the first active use.
loading can happen whenever the JVM likes. initialization happens at a moment the spec pins down precisely, and that's the part that bites.
the picture
you know how a static block sometimes runs way later than you'd expect, and sometimes not at all? that's because "the class is loaded" and "the class is initialized" are two different events, and only the second one runs your code.
think of a new hire. their paperwork arriving is loading. the background check is verification. getting a desk with empty drawers is preparation. learning where the printer is, the first time they need it, is resolution. and orientation (where they actually do something) only happens on the first day they're asked to work: that's initialization.
hover a stage.
watch a real run
here's a small program, run with -Xlog:class+load=info,class+init=info, which makes the JVM narrate exactly this. the log lines in the figure are copied from that run.
- 1class Base {
- 2 static { System.out.println("Base <clinit>"); }
- 3}
- 4
- 5class Price extends Base {
- 6 static int tax = 18;
- 7 static { System.out.println("Price <clinit>"); }
- 8}
- 9
- 10public class Shop {
- 11 public static void main(String[] args) {
- 12 System.out.println("main starts");
- 13 Price[] none = new Price[2];
- 14 System.out.println("made an array");
- 15 System.out.println("tax = " + Price.tax);
- 16 }
- 17}
·[0.017s] class,load Shop source: file:.../[0.018s] class,init Start class verification for: Shop[0.018s] class,init End class verification for: Shop[0.018s] class,init 298 Initializing 'Shop'(no method)
before main can run, Shop is loaded, verified and initialized. "(no method)" means it has no static initializer to run. it's the 298th class initialized: the JDK did the other 297.
new Price[2] on line 13: both classes get loaded, and nothing else happens. the green phase only lights up when Price.tax is actually read.two things that log proves. first, an array of Price loads Price without initializing it. second, the JVM verified Base and Price right before initializing them, not when they were loaded: HotSpot links lazily too.
the exact moments
the JVM spec (§5.5) lists what counts as a first active use. a class or interface is initialized just before the first:
- new
- creating an instance (
new). - static fields
- reading or writing a static field declared in that class (
getstatic,putstatic). constants don't count, see below. - static calls
- calling a static method (
invokestatic). - handles
- the first invocation of a method handle to one of the above. looking it up isn't enough.
- reflection
Class.forName(name)(the 1-arg form),Method.invokeon a static method,Field.geton a static field.- subclasses
- initializing a class initializes its superclass first (and superinterfaces that declare default methods).
- main
- the class whose
mainthe launcher runs.
everything else (class literals, arrays of the type, casts, instanceof, reflection over the shape) may load the class, but won't initialize it. try your instincts:
answer, then see what the real program printed.
A.CONST, B.counter, and the interfaces at the end.preparation, then initialization
preparation and initialization are separate steps, and you can catch a class in between them. <clinit> runs the static initializers in source order, and anything that reads a static before its line has run sees the zero that preparation put there.
class Counter {
static Counter INSTANCE = new Counter();
static int count = 10;
Counter() { count++; }
}what does Counter.count print?
class Counter {
static Counter INSTANCE = new Counter();
static int count = 10;
Counter() { count++; }
}
public class Main {
public static void main(String[] args) {
System.out.println(Counter.count);
}
}10
verification is a real gate
javac never produces code that fails verification, so you rarely meet the verifier. but anything that writes bytecode can get it wrong: a bytecode library with a bug, an agent, a hand-built class. here's a method that promises to return a String and returns an int instead, built with the Class-File API:
void main() throws Throwable {
// a method that promises to return a String, then returns the int 1
byte[] bytes = ClassFile.of().build(ClassDesc.of("Bad"), cls -> cls
.withMethodBody("name", MethodTypeDesc.of(ConstantDescs.CD_String),
ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC,
code -> code.iconst_1().areturn()));
IO.println("built Bad, defining it...");
try {
MethodHandles.lookup().defineClass(bytes);
IO.println("never printed");
} catch (VerifyError e) {
IO.println(e.getClass().getName() + ": " + e.getMessage().lines().findFirst().orElseThrow());
}
}built Bad, defining it... java.lang.VerifyError: Bad type on operand stack
this one needs a real JVM (reflection), so it doesn't run in the browser. copy it into java Main.java to try it.
the full message is worth reading once, because it's the verifier showing its work:
java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
Bad.name()Ljava/lang/String; @1: areturn
Reason:
Type integer (current frame, stack[0]) is not assignable to reference type
Current Frame:
bci: @1
flags: { }
locals: { }
stack: { integer }
Bytecode:
0000000: 04b004 b0 is the whole method: iconst_1, areturn. without the verifier, the JVM would hand the integer 1 to the caller as if it were a pointer to a String, and the first method call on it would crash the process, or worse, not crash.
the initialization lock
the JVM guarantees <clinit> runs once, even if ten threads hit the class at the same moment. it does that with a per-class initialization lock: the first thread runs <clinit>, everyone else waits. which means two classes whose static initializers need each other can deadlock:
public class Deadlock {
static class Left {
static final int VALUE;
static {
pause();
VALUE = Right.VALUE + 1;
}
}
static class Right {
static final int VALUE;
static {
pause();
VALUE = Left.VALUE + 1;
}
}
static void pause() {
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
public static void main(String[] args) {
System.out.println("pid " + ProcessHandle.current().pid());
new Thread(() -> System.out.println(Left.VALUE), "t1").start();
new Thread(() -> System.out.println(Right.VALUE), "t2").start();
}
}it prints its pid and then nothing, ever. here's jcmd <pid> Thread.print from the real run:
"t1" #25 [43011] prio=5 os_prio=31 cpu=0.34ms elapsed=2.23s ... in Object.wait()
java.lang.Thread.State: RUNNABLE
at Deadlock$Left.<clinit>(Deadlock.java:6)
- waiting on the Class initialization monitor for Deadlock$Right
at Deadlock.lambda$main$0(Deadlock.java:21)
"t2" #26 [42755] prio=5 os_prio=31 cpu=0.23ms elapsed=2.23s ... in Object.wait()
java.lang.Thread.State: RUNNABLE
at Deadlock$Right.<clinit>(Deadlock.java:13)
- waiting on the Class initialization monitor for Deadlock$Left
at Deadlock.lambda$main$1(Deadlock.java:22)- load, link (verify, prepare, resolve), initialize. only the last one runs your code.
- initialization happens at the first new, static field use, static call, or subclass init. not earlier.
- compile-time constants are copied into callers. changing one means recompiling everyone.
- statics start at zero and get assigned in source order. read one too early and you see the zero.
<clinit>runs once, under a lock. two classes that need each other at init time can deadlock, silently.