How does class loading work in Java, and what are the three stages?
Class loading has three stages, and each has a distinct job.
Loading finds the bytes for the class and defines a java.lang.Class object for it. The bytes come from somewhere the loader knows about — the runtime image, a jar, the classpath, or a custom source. For most classes the loading is lazy: the class is not loaded until the program first uses it, which is why a program with an unreachable branch referencing a missing class still runs.
Linking prepares the class for execution, in three sub-steps. Verification checks that the bytecode obeys the type rules and cannot do anything unsafe — this is the step that stops a hand-crafted class file from reaching outside the sandbox. Preparation allocates memory for the static fields and sets them to their default values, so a static int is zero at this point even if the source says it starts at 42. Resolution replaces the symbolic references in the constant pool with direct references to the classes, methods and fields they name.
Initialisation runs the class's static initialiser blocks and static field initialisers, in source order, and then the class is ready to be used. This is the point at which a static int finally becomes 42.
Two details worth remembering. A class is initialised exactly once, and the JVM guarantees that by locking on the class's own monitor, so two threads racing to initialise the same class cannot both run the initialiser. And initialisation is triggered on first active use — instantiating it, accessing a static field, or invoking a static method — not merely by loading it.
Here is the same thing as runnable code:
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> c = Main.class;
System.out.println("name : " + c.getName());
System.out.println("loader : " + c.getClassLoader());
System.out.println("superclass : " + c.getSuperclass().getName());
System.out.println("package : " + c.getPackageName());
System.out.println("interfaces : " + c.getInterfaces().length);
// Loading happens on first use.
Class<?> loaded = Class.forName("java.lang.String");
System.out.println("loaded : " + loaded.getName());
System.out.println("same class : " + (loaded == String.class));
}
}