The life cycle of a java class includs 5 stages : load, link, initialize, use, unload.
The first three stages overall is the class loading process (loading + linking + initialization) :

What You Need
- About 16 minutes
- A favorite text editor or IDE
- Java 8 or later
- Loading – find and load class binary data
- Linking [Verify] – ensure correctness of loaded classes
- Linking [Prepare] – allocate memory for static variables of the class and initialize them to default values
- Linking [Resolve] – convert symbolic references in classes to direct references
- Initialization – initialize class static variables
- Use – access class data structure in method area and the class object in heap area
- Unload – garbage collect class data structure in method area and the class object in heap area
Loading – find and load class binary data
It is the first phase of class loading process.
During this phase, the JVM needs to do three things :
- Get the binary byte stream defined by a class by its fully qualified name;
- Convert the static storage structure represented by this byte stream into the runtime data structure of the method area;
- A java.lang.Class object representing this class is generated in the Java heap as the access entry to the data in the method area.

Compared to other phases, the loading phase is the most controllable stage, as developers can use the class loader to complete the loading or customize the loader to complete the loading.
How to load .class files :
- load directly from the local system;
- download the .class file from the network;
- load .class files from archives like zip, jar, etc;
- extract .class files from database;
- compile java source files dynamically into .class files.
To view all the loaded classes, we can use jvm flag -XX:+TraceClassLoading, for instance, java -XX:+TraceClassLoading Test
Below is an example of the output with this flag enabled when running a Java program :
[0.030s][info][class,load] java.lang.Object source: shared objects file
[0.031s][info][class,load] java.io.Serializable source: shared objects file
[0.031s][info][class,load] java.lang.Comparable source: shared objects file
[0.031s][info][class,load] java.lang.CharSequence source: shared objects file
[0.031s][info][class,load] java.lang.constant.Constable source: shared objects file
[0.031s][info][class,load] java.lang.constant.ConstantDesc source: shared objects file
[0.031s][info][class,load] java.lang.String source: shared objects file
[0.031s][info][class,load] java.lang.reflect.AnnotatedElement source: shared objects file
[0.031s][info][class,load] java.lang.reflect.GenericDeclaration source: shared objects file
[0.031s][info][class,load] java.lang.reflect.Type source: shared objects file
[0.031s][info][class,load] java.lang.invoke.TypeDescriptor source: shared objects file
[0.031s][info][class,load] java.lang.invoke.TypeDescriptor$OfField source: shared objects file
[0.031s][info][class,load] java.lang.Class source: shared objects file
[0.031s][info][class,load] java.lang.Cloneable source: shared objects file
[0.031s][info][class,load] java.lang.ClassLoader source: shared objects file
[0.031s][info][class,load] java.lang.System source: shared objects file
[0.031s][info][class,load] java.lang.Throwable source: shared objects file
[0.031s][info][class,load] java.lang.Error source: shared objects file
[0.031s][info][class,load] java.lang.Exception source: shared objects file
[0.031s][info][class,load] java.lang.RuntimeException source: shared objects file
[0.031s][info][class,load] java.lang.SecurityManager source: shared objects file
Linking [Verify] – ensure correctness of loaded classes
Verify is the first step in the linking phase.
The purpose of this step is to ensure that the information contained in the byte stream of the class file meets the requirements of the current java virtual machine and will not compromise its security.
This step includes below verifications :
- Format Validation : verify whether the byte stream conforms to the specification of the class file format.
For instance, whether it starts with 0xCAFEBABE, whether the major and minor version numbers are within the processing range of the current java virtual machine, whether the constants in the constant pool have unsupported types; - Metadata Validation : perform semantic analysis on the information described by the bytecode to ensure that the described information meets the requirements of the java language specification.
For instance, whether this class has a parent class, except java.lang.Object; - Bytecode Validation : the method of the class will be analyzed to ensure that the verified method will not do behaviors that endanger the security of the java virtual machine during runtime.
For instance, it is safe to assign a subclass object to the superclass data type, however, it is illegal to assign a parent class object to a subclass data type, or even to a completely unrelated type; - Symbolic Reference Validation : mainly to check the matching of information for various symbolic references in the constant pool.
For instance, whether the corresponding class can be found by the fully qualified name described by the string in the symbolic reference.
The verify step is very important, but not necessary.
It is possible to use -Xverifynone to turn off most of the class verification measures to shorten the java virtual machine class loading time.
Linking [Prepare] – allocate memory for static variables of the class and initialize them to default values
The prepare is the step in which memory is formally allocated for class variables and initial values for those variables are set, all of which will be allocated in the method area.
There are a few things to note about this step :
- Only static variables of the class are allocated for memory allocation, not instance variables.
Instance variables will be allocated in the java heap along with the object when it is instantiated; - The initial value here is usually the default value of the data type.
Below are the default values of different data types :
DATA TYPE | DEFAULT VALUE |
---|---|
int | 0 |
long | 0L |
short | (short) 0 |
char | ‘\u0000’ |
byte | (byte) 0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
- Suppose the definition of a class variable is : public static int value = 6, then the initial value of the variable value after the prepare step is 0 not 6, because no java method has been executed at this time, and the put staticinstruction to assign value to 6 is stored in the class constructor <clinit>() method after compiling, so it will not be executed until the initialization phase;
- But there is an exception, if a class variable is final, for instance, final static int value = 321, then after the prepare step, the value is assigned to 321.
Linking [Resolve] – convert symbolic references in classes to direct references
Symbolic References : it is a set of symbols to describe the referenced target.
Symbols can be literals in any form, as long as they can be used to locate the target without ambiguity.
Symbolic references have nothing to do with the memory layout implemented by the java virtual machine, and the target of the reference is not necessarily already loaded into memory.
Direct References : it can be a pointer directly to the target, a relative offset, or a handle that can be located indirectly to the target.
The direct reference is related to the memory layout of the jvm implementation, and the direct reference translated from the same symbolic reference on different java virtual machine instances is generally not the same.
If there is a direct reference, the target of the reference must already exist in memory.
This step is mainly aimed at four types of symbolic references : class or interface, field, class method, and interface method.
Initialization – initialize class static variables
In the linking phase described above, the static class variables have been assigned initial values in the prepare step.
In the initialization phase, the static class variables are initialized according to the programmer’s code.
In other words, the initialization phase is the process of executing the <clinit>() method.
But when a static class variable has final modifier, it becomes actually a constant.
When the value of a constant is :
- primitive types;
- string literal;
- not depend on a method call.
Then its initialization is done at prepare step of linking phase other than in the initialization phase.
Below is an example for a better understanding of this point :
import java.util.Random;
public class InitializationTest {
// instance variables are not initialized in the initialization phase
// only static class variables are
private int a = 0;
// A is in <clinit>()
private static int A = 10;
// B is not in <clinit>() bcz programmer does not initialize it explicitly
private static int B;
// C is in <clinit>() bcz programmer initialize its value explicitly in a static
// block
private static int C;
static {
C = 20;
}
// D is not in <clinit>()
// bcz it has final modifier, so it is a constant
// its initialization has been done at the prepare step of linking phase
private static final int D = 30;
// E is in <clinit>()
// even if it has final modifier (it is a constant)
// but its value is not primitive type
// so at the prepare step of linking phase, it can not be defined
// it is why it is been initialized in the initialization phase
private static final int E = Integer.valueOf(40);
// F is in <clinit>()
// even if it has final modifier (it is a constant)
// even if its value is primitive type
// but its value needs to call method to be determined
// so at the prepare step of linking phase, it can not be defined
// it is why it is been initialized in the initialization phase
private static final int F = new Random().nextInt(50);
// G is not in <clinit>()
// bcz it has final modifier, so it is a constant
// even if its value is not primitive type
// but string literal is initialized at the prepare step of linking phase
// in constant pool of class file, we can find ConstantValue for it
private static final String G = "hello";
// H is in <clinit>()
// even if it has final modifier (it is a constant)
// but its value is not primitive type nor string literal
// so at the prepare step of linking phase, it can not be defined
// it is why it is been initialized in the initialization phase
private static final String H = new String("world");
}
We can observe the same by checking <clinit>() method in the class file :

The <clinit>() method is generated by the compiler automatically.
This method collects the assignment of all class variables in the class as well as the combination of the statements in the static statement block (static{}).
The order of the compiler collection is determined by the statement in the source file.
Only the variables defined before the static statement block can be accessed, and the variables defined after it can be assigned values in the preceding static statement block, but cannot be accessed.
Below is an example :
1public class InitializationTest2 {
2 static {
3 A = 1;
4 // accesse of A is not allowed here
5 // bcz it is defined after this static block
6 // comment below will solve it
7 System.out.println(A);
8 }
9
10 // in case of keeping line 7
11 // put below in front of static block
12 // will solve it as well
13 private static int A;
14}
In above code snippet, the line 7 contains compilation error as below :
Cannot reference a field before it is defined Java(570425419)
The <clinit>() method of the parent class is executed first, the static statement block defined in the parent class takes precedence over the variable assignment operation of the child class.
Below is an example :
public class InitializationTest3 {
public static void main(String[] args) {
System.out.println("B in Son = " + Son.B);
}
private static class Father {
public static int A;
static {
A = 1;
System.out.println("In static block of Father");
}
}
private static class Son extends Father {
public static int B;
static {
B = 2;
System.out.println("In static block of Son");
System.out.println("A in Father = " + A);
}
}
}
The output of above code snippet is below :
In static block of Father
In static block of Son
A in Father = 1
B in Son = 2
The virtual machine ensures that the <clinit>() method of a class is correctly locked and synchronized in a multi-threaded environment.
If multiple threads initialize a class at the same time, only one thread will execute the <clinit>() method of this class, and other threads need to block and wait until the active thread finishes executing the <clinit>() method.
Below is an example :
public class InitializationTest4 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("t1 : A's ID = " + A.ID);
});
t1.setName("t1");
Thread t2 = new Thread(() -> {
System.out.println("t2 : B's ID = " + B.ID);
});
t2.setName("t2");
t1.start();
t2.start();
t1.join();
t2.join();
}
private static class A {
public static int ID = 1;
static {
System.out.println("A : B's ID = " + B.ID);
}
}
private static class B {
public static int ID = 2;
static {
System.out.println("B : A's ID = " + A.ID);
}
}
}
As you can see that once above test class is running, it hangs and there is no output at all.
If we understand how <clinit>() works, it is easy to understand the reason.
In thread 1, it calls <clinit>() of A, in thread 2, it calls <clinit>() of B.
In <clinit>() of A, it calls <clinit>() of B, in <clinit>() of B, it calls <clinit>() of A.
Since in <clinit>(), it allows only one thread at a time, so the above situation creates a deadlock which we can not even explicitly observe in the thread dump.
When we look into the thread dump, there is no sign neither to indicate explicitly the reason, both of the two threads are in runnable status :
"t1" #19 [8257] prio=5 os_prio=0 cpu=1,67ms elapsed=16736,23s tid=0x000074a5a8190320 nid=8257 waiting on condition [0x000074a57c62d000]
java.lang.Thread.State: RUNNABLE
at InitializationTest4$A.<clinit>(InitializationTest4.java:23)
- waiting on the Class initialization monitor for InitializationTest4$B
at InitializationTest4.lambda$0(InitializationTest4.java:4)
at InitializationTest4$$Lambda/0x000074a530000a00.run(Unknown Source)
at java.lang.Thread.runWith(java.base@21.0.3/Thread.java:1596)
at java.lang.Thread.run(java.base@21.0.3/Thread.java:1583)
"t2" #20 [8258] prio=5 os_prio=0 cpu=1,08ms elapsed=16736,23s tid=0x000074a5a8191560 nid=8258 waiting on condition [0x000074a57c52d000]
java.lang.Thread.State: RUNNABLE
at InitializationTest4$B.<clinit>(InitializationTest4.java:30)
- waiting on the Class initialization monitor for InitializationTest4$A
at InitializationTest4.lambda$1(InitializationTest4.java:9)
at InitializationTest4$$Lambda/0x000074a530000c18.run(Unknown Source)
at java.lang.Thread.runWith(java.base@21.0.3/Thread.java:1596)
at java.lang.Thread.run(java.base@21.0.3/Thread.java:1583)
When is the initialization of the class triggered?
- when initializing the target class specified by new :
public class InitializationTest5 {
public static void main(String[] args) {
A a = new A();
}
private static class A {
static {
System.out.println("A has been initialized!");
}
}
}
Below is the output of above code snippet :
A has been initialized!
- when calling a static variable of class, except that the variable has final modifier and its value is not primitive nor string literal :
public class InitializationTest6 {
public static void main(String[] args) {
System.out.println(A.ID);
}
private static class A {
public static int ID = 1;
static {
System.out.println("A has been initialized!");
}
}
}
Below is the output of above code snippet :
A has been initialized!
1
- when calling a static method of class :
public class InitializationTest7 {
public static void main(String[] args) {
A.greeting();
}
private static class A {
static {
System.out.println("A has been initialized!");
}
public static void greeting() {
System.out.println("hello this is A");
}
}
}
Below is the output of above code snippet :
A has been initialized!
hello this is A
- when using the reflection API to make a reflection call to a class :
public class InitializationTest8 {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("InitializationTest8$A");
}
private static class A {
static {
System.out.println("A has been initialized!");
}
}
}
Below is the output of above code snippet :
A has been initialized!
- when deserializing the object of a class :
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class InitializationTest9 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// first time run this to create the serialized file
// then comment it out to test the deserialization
// serialize();
try (FileInputStream fi = new FileInputStream(new File("a"));
ObjectInputStream oi = new ObjectInputStream(fi)) {
A a = (A) oi.readObject();
}
}
private static class A implements Serializable {
static {
System.out.println("A has been initialized!");
}
}
private static void serialize() throws IOException, FileNotFoundException {
A a = new A();
try (FileOutputStream f = new FileOutputStream(new File("a"));
ObjectOutputStream o = new ObjectOutputStream(f)) {
o.writeObject(a);
}
}
}
Below is the output of above code snippet :
A has been initialized!
- the initialization of the subclass will trigger the initialization of the parent class :
public class InitializationTest10 {
public static void main(String[] args) {
System.out.println(Son.ID);
}
private static class Father {
static {
System.out.println("Father has been initialized!");
}
}
private static class Son extends Father {
public static int ID = 1;
static {
System.out.println("Son has been initialized!");
}
}
}
Below is the output of above code snippet :
Father has been initialized!
Son has been initialized!
1
- if an interface defines the default method, the initialization of the class that directly or indirectly implements the interface will trigger the initialization of the interface :
public class InitializationTest11 {
public static void main(String[] args) {
System.out.println(A.ID);
}
private static interface I {
Thread t = new Thread() {
{
System.out.println("t has been initialized, so I must have been initialized!");
}
};
default void greeting(){
}
}
private static class A implements I {
public static int ID = 1;
static {
System.out.println("A has been initialized!");
}
}
}
Below is the output of above code snippet :
t has been initialized, so I must have been initialized!
A has been initialized!
1
- when the virtual machine starts, initializing the class specified by the user :
public class InitializationTest12 {
static {
System.out.println("InitializationTest12 has been initialized!");
}
public static void main(String[] args) {
}
}
Below is the output of above code snippet :
InitializationTest12 has been initialized!
Use – access class data structure in method area and the class object in heap area
Use is the most common stage for a java developer, every day they use java class object or its instances to program.
Unload – garbage collect class data structure in method area and the class object in heap area
The last stage unload consist of doing the garbage collection for the loaded classes.
It is not common that the loaded classes being unloaded when jvm is still alive.
To acheive so, all the related links should be cut off.
Like showed below, all the red arrow links should be broken :

When jvm is ended, all the loaded classes are unloaded.
Below situations will end a jvm :
- The System.exit() method is executed;
- Program execution ends normally;
- The program encounters an exception or error during execution and terminates abnormally;
- Operating system error leads jvm to end.