The labs on this site need JavaScript to run. The same content is written out below.
Learn › Java › JVM
Class loading and initialization
When does the static initializer of a class run?
A static initializer written in a class does not run when the program starts. It runs once, when the class is first "really" used. But not every appearance of the class name in the code counts as that kind of use.
Checked against real execution · The 18 combinations were newly compiled and run on JDK 21 and checked against the results
Concepts in this lab
- 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).
- Class initialization
- This is the stage in which the static fields of a class are given their initial values and its static initializers are run. It is a different stage from loading, which reads the class in, and it happens at the moment the class is first actually used, for example when an object of the class is created, a static method is called, or a static field that is not a constant is used. It happens only once per class, and when a subclass is initialized, its superclass is initialized first. Creating an array, using ClassName.class, and reading a compile-time constant do not trigger initialization.
- Static initializer
- This is a block of code written inside a class in the form static { ... }, and it runs once when the class is initialized. It is used when the value to put into a static field has to be computed with several lines of code. The initializer expressions of static fields and the static initializers run from top to bottom, in the order in which they are written in the source. Unlike a constructor, which runs every time an object is created, it does not run again no matter how many objects are created.
- Compile-time constant
- This is a variable, such as static final int MAX = 10, that is final, has a primitive type or the type String, and gets its value from a constant expression such as a literal. The compiler puts the value directly into the places that use such a constant, so at run time the class that declares the constant is not looked up and is not initialized either. A field whose value comes from a method call is not a constant even if it is final, because its value cannot be known at compile time. Because the value is put in directly, when you change the value of a constant you must also recompile the code that uses it for the new value to take effect.
- static
- This keyword marks a member as belonging to the class, not to an instance. A static field exists only once per class and is shared by all instances, and a static method is called without creating an object. A static field whose value changes is effectively a global variable, so it easily causes problems when several threads access it at the same time.
Try these
- Default settings — Reading a constant. The static initializer does not run, and the class is not even loaded.
- Creating an object with new — The static initializer runs only once, when the first object is created.
- A static final field that is not a constant — Its value is known only at run time, so initialization happens.
- Creating only an array — The class is only loaded and is not initialized.
- Creating an object of the subclass — The superclass is initialized first, and then the subclass.
- A static field of the superclass through the subclass name — The code says Child.count, but Child is not initialized.
- Using ClassName.class — It only refers to the class information and does not initialize the class.
Running with the default settings
- System.out.println("main starts"): The program has started. It has not yet reached any code that uses Parent or Child, so the JVM has neither loaded the two classes nor run a static initializer.
- int a = Parent.MAX: MAX is a constant whose value is fixed at compile time. The compiler has already turned this line into the same thing as int a = 10; so at run time there is no need to look up the Parent class. The class is not loaded, and its static initializer does not run.
- System.out.println("first use done"): Number of times the static initializers have run so far: Parent 0, Child 0.
- int b = Parent.MAX: Again the value 10 is used directly. No matter how many times the constant is read, the class is not initialized.
- System.out.println("second use done"): The program ends. Number of times the static initializers ran: Parent 0, Child 0.
Result of this run
- A field that is static final and whose value is fixed at compile time, as with a number or string literal, is a "compile-time constant". The compiler puts the value directly into the code that uses the constant, so the class is not used at run time. This is why, if you change only the constant and recompile only that class, the old value stays in the code that uses it.
Code
class Parent {
static final int MAX = 10; // a constant whose value is fixed at compile time
static { System.out.println("Parent init"); } // static initializer
}
class Child extends Parent {
static { System.out.println("Child init"); }
}
System.out.println("main starts");
int a = Parent.MAX;
System.out.println("first use done");
int b = Parent.MAX;
System.out.println("second use done");
Assumptions of this model
For each of the 18 combinations, source code identical to the code on the screen was newly compiled and run on JDK 21 on this PC, and the order of the printed lines and the classes that were loaded were checked against the model. The point at which each class was loaded was checked against the JVM log obtained with the -Xlog:class+load option. When initialization happens is defined by the Java Language Specification, so it is the same on every JVM. When a class is loaded, in contrast, is left to the JVM implementation, so "a class is loaded when it is first used" and "a class is not even loaded when only a constant is read" are behaviors confirmed on the JVM of this PC (HotSpot). That reading the constant is compiled into the value 10 was confirmed by looking at the bytecode with javap. The detailed steps that a class goes through after loading and before initialization, such as verification and preparation, and the initialization rules for Class.forName and for interfaces are not covered.
Previous lab: JIT warmup · Next lab: I/O streams and buffers