Fundamentals 16 min read

Understanding the Lifecycle of Java Classes in the JVM

This article explains the complete lifecycle of a Java class within the JVM, covering memory areas such as the method, heap, and stack regions, and detailing the five stages—loading, linking, initialization, usage, and unloading—along with code examples illustrating active and passive references.

Java Captain
Java Captain
Java Captain
Understanding the Lifecycle of Java Classes in the JVM

Introduction

A careful reader asked about the lifecycle of Java classes, and the author noticed that most Chinese Java textbooks explain "how" but not "why". This article therefore explains the JVM memory areas involved in a class's lifecycle and walks through each phase of that lifecycle.

JVM Memory Areas

Method Area – stores loaded class information, constants, static variables, and method bytecode.

Constant Pool – a part of the method area that holds literals and symbolic references.

Heap – holds object instances of classes.

Stack – the JVM stack composed of stack frames that store local variables, method return addresses, etc., created when a method is invoked and discarded when it returns.

Other runtime areas such as the native method stack and program counter exist but are not directly related to class lifecycle.

Class Lifecycle

After a Java source file is compiled into a .class byte‑code file, the class goes through five stages: loading, linking, initialization, usage, and unloading. An illustration (omitted) shows the flow.

Loading

During loading the JVM finds the required .class file (or reads it from a JAR) and loads its binary data into the method area, then creates a java.lang.Class object on the heap as an entry point.

Common loading methods include:

Fetching the class file by its fully‑qualified name.

Reading it from a JAR archive.

Loading from the network (e.g., old applets).

Generating it at runtime (e.g., dynamic proxies).

Loading non‑class files that are first transformed into bytecode.

Most JVMs, including HotSpot, load a class lazily—only when the JVM predicts the class will be used.

Linking

Linking validates the class file, prepares static fields with default values, and resolves symbolic references to direct references.

Verification checks class file format, duplicate members, type correctness, etc.

Preparation allocates memory for static variables and assigns JVM default values (0 for primitives, null for references, and the programmer‑specified value for final static constants).

Resolution converts symbolic references in the constant pool to direct memory addresses (e.g., turning the symbolic method name show into a concrete address like c17164 ).

Initialization

Initialization runs static initializers and static field assignments. It is triggered by an active reference such as:

Instantiating an object with new .

Reading or writing a static field.

Invoking a static method.

Reflective operations that access the class.

Initializing a subclass (which also initializes its superclass).

Running the class’s main method.

Example code that demonstrates active references (the code is shown unchanged):

import java.lang.reflect.Field;
import java.lang.reflect.Method;

class InitClass {
    static {
        System.out.println("初始化InitClass");
    }
    public static String a = null;
    public static void method() {}
}

class SubInitClass extends InitClass {}

public class Test1 {
    /**
     * 主动引用引起类的初始化的第四种情况就是运行Test1的main方法时
     * 导致Test1初始化,这一点很好理解,就不特别演示了。
     * 本代码演示了前三种情况,以下代码都会引起InitClass的初始化,
     * 但由于初始化只会进行一次,运行时请将注解去掉,依次运行查看结果。
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        //  主动引用引起类的初始化一: new对象、读取或设置类的静态变量、调用类的静态方法。
        //  new InitClass();
        //  InitClass.a = "";
        //  String a = InitClass.a;
        //  InitClass.method();
        //  主动引用引起类的初始化二:通过反射实例化对象、读取或设置类的静态变量、调用类的静态方法。
        //  Class cls = InitClass.class;
        //  cls.newInstance();
        //  Field f = cls.getDeclaredField("a");
        //  f.get(null);
        //  f.set(null, "s");
        //  Method md = cls.getDeclaredMethod("method");
        //  md.invoke(null, null);
        //  主动引用引起类的初始化三:实例化子类,引起父类初始化。
        //  new SubInitClass();
    }
}

The initialization order follows the textual order of static fields and static blocks, with superclass static initializers executing before subclass ones.

Another example shows the exact order of execution:

public class Field1 {
    public Field1() {
        System.out.println("Field1构造方法");
    }
}

public class Field2 {
    public Field2() {
        System.out.println("Field2构造方法");
    }
}
class InitClass2 {
    static {
        System.out.println("运行父类静态代码");
    }
    public static Field1 f1 = new Field1();
    public static Field1 f2;
}

class SubInitClass2 extends InitClass2 {
    static {
        System.out.println("运行子类静态代码");
    }
    public static Field2 f2 = new Field2();
}

public class Test2 {
    public static void main(String[] args) throws ClassNotFoundException {
        new SubInitClass2();
    }
}

Only static assignments and static blocks are executed during initialization; non‑static code runs when an instance is created.

Usage

Usage consists of active and passive references. Active references trigger initialization (as described above). Passive references do not, for example:

Referencing a superclass’s static field only initializes the superclass.

Creating an array of a class type does not initialize the class.

Accessing a final static constant does not trigger initialization.

Passive reference example (code kept intact):

class InitClass {
    static {
        System.out.println("初始化InitClass");
    }
    public static String a = null;
    public final static String b = "b";
    public static void method() {}
}

class SubInitClass extends InitClass {
    static {
        System.out.println("初始化SubInitClass");
    }
}

public class Test4 {
    public static void main(String[] args) throws Exception {
        // String a = SubInitClass.a; // triggers InitClass initialization only
        // String b = InitClass.b;   // constant reference, no initialization
        SubInitClass[] sc = new SubInitClass[10]; // class array, no initialization
    }
}

In summary, the usage phase includes both active and passive references; only active references cause class initialization.

Unloading

A class is unloaded when all its instances are garbage‑collected, its defining ClassLoader is reclaimed, and no live java.lang.Class objects reference it. At that point the JVM clears the class information from the method area.

Conclusion

Java developers are usually familiar with object lifecycles, but a class’s lifecycle is broader: loading, linking, and initialization happen before any object is created, and the class remains in memory until the three unloading conditions are met. Understanding these phases helps diagnose class‑loading issues and improves JVM performance.

JavaJVMMemory ManagementInitializationClass LoadingStatic Initialization
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.