Fundamentals 8 min read

Mastering Java Class Loading: Lifecycle, Loaders, and Delegation Explained

This article explains Java's class loading mechanism in depth, covering the class lifecycle stages, the three primary class loaders, the parent‑delegation model, caching behavior, and practical code examples for custom loaders and dynamic loading methods.

Thoughts on Knowledge and Action
Thoughts on Knowledge and Action
Thoughts on Knowledge and Action
Mastering Java Class Loading: Lifecycle, Loaders, and Delegation Explained

Class Lifecycle

The JVM processes a class through five distinct phases: loading, linking, initialization, usage, and unloading. Linking is further divided into verification, preparation, and resolution.

Loading : The class file is located via its fully‑qualified name on the classpath, its byte stream is read, and a Class object is created in the method area.

Verification : The bytecode is checked against JVM specifications for format, metadata, and symbolic references.

Preparation : Memory is allocated for static fields and default values are assigned.

Resolution : Symbolic references (e.g., class, method, field names) are resolved to direct references.

Initialization : Static initializers and <clinit> blocks are executed; superclass initialization occurs first.

Usage : The class is used by the application through method calls and field accesses.

Unloading : When no live references remain, the class and its metadata are removed from the heap.

Class Loaders

Java defines three built‑in class loaders:

Bootstrap Loader (BootstrapLoader) : Implemented in native code, it loads core Java libraries from jre/lib.

Extension Loader (ExtClassLoader) : Implemented by sun.misc.Launcher$ExtClassLoader, it loads classes from jre/lib/ext and directories specified by -Djava.ext.dirs.

Application Loader (AppClassLoader) : Implemented by sun.misc.Launcher$AppClassLoader, it loads classes found on the application’s classpath.

Developers can create custom loaders by extending java.lang.ClassLoader and overriding findClass:

public class MyClassLoader extends ClassLoader {
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name); // load custom class file
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        return defineClass(name, classData, 0, classData.length);
    }
}

Loading Mechanism

When a class loader loads a class, it also loads all classes referenced by that class. The parent‑delegation model ensures that a child loader first asks its parent to load a class; only if the parent cannot find it does the child attempt loading.

A caching mechanism stores already loaded classes, so subsequent requests are served from the cache. Modifying a class file requires a JVM restart for the changes to take effect.

Parent Delegation Model

The AppClassLoader delegates loading requests to ExtClassLoader. ExtClassLoader delegates to BootstrapLoader.

If BootstrapLoader cannot find the class (e.g., not in $JAVA_HOME/jre/lib), ExtClassLoader attempts loading.

If both fail, AppClassLoader tries; failure results in ClassNotFoundException.

Dynamic Loading Methods

Class.forName() : Loads the class, triggers static initialization, and executes static blocks.

public static void main(String[] args) throws Exception {
    System.out.println("before class.forName");
    Class c = Class.forName("cn.it.server.Test"); // static block executed
    System.out.println("after class.forName");
    System.out.println("before newInstance...");
    Test info2 = (Test) c.newInstance(); // constructor invoked
    System.out.println("after newInstance...");
}

ClassLoader.loadClass() : Loads the class without executing static blocks; static code runs only when newInstance() is called.

public static void main(String[] args) throws Exception {
    System.out.println("before loadClass...");
    Class c = Test.class.getClassLoader().loadClass("cn.it.server.Test");
    System.out.println("after loadClass...");
    System.out.println("before newInstance...");
    Test info1 = (Test) c.newInstance(); // static block executed now
    System.out.println("after newInstance...");
}

Both methods ultimately result in class loading, linking, and initialization, but Class.forName performs static initialization immediately, whereas loadClass defers it until an instance is created.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaJVMDynamic LoadingCustom ClassLoaderclass loadingParent Delegation
Thoughts on Knowledge and Action
Written by

Thoughts on Knowledge and Action

Travel together, with knowledge and action all the way

0 followers
Reader feedback

How this landed with the community

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.