Fundamentals 11 min read

Demystifying Java Class Loading: Mechanisms, Types, and Custom Loaders

This article explains Java's class loading mechanism, the three built‑in class loaders, the parent‑delegation model, reasons for using it, how to create custom loaders, and the detailed phases of loading, linking, initialization, usage, and unloading.

Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Demystifying Java Class Loading: Mechanisms, Types, and Custom Loaders

What is the class loading mechanism?

In simple terms, class loading is the process of taking Java source files ( .java) compiled into bytecode files ( .class), loading them into the JVM memory, placing them in the method area, and creating a java.lang.Class object. Only after loading can we instantiate objects of the class.

Types of class loaders

The JVM provides three default class loaders:

Bootstrap ClassLoader : the top‑level loader that loads core libraries from %JRE_HOME%/lib such as rt.jar, charsets.jar, etc. It is part of the JVM, written in C++, and cannot be accessed directly from Java code.

Extension ClassLoader : loads extension libraries from %JRE_HOME%/lib/ext. It can also load libraries from directories specified by the -Djava.ext.dirs option.

Application ClassLoader (also called System ClassLoader): the default loader for user‑written classes and third‑party libraries located in the classpath. Its loading path can be set with -Djava.class.path.

When a class needs to be loaded, the three loaders work in the order shown in the diagram below.

The loading process follows the parent‑delegation model ( Parents Delegation Model): a request is first delegated to the parent loader; if the parent cannot load the class, the child loader attempts it.

Why use the parent‑delegation model?

1. Ensure class uniqueness

The model guarantees that a class, such as java.lang.Object, is loaded only once by the Bootstrap ClassLoader, preventing duplicate loading and ensuring global uniqueness.

2. Protect core library security

Core libraries (e.g., java.lang.*, java.util.*) are loaded by the bootstrap loader, preventing user‑defined classes from overriding them and preserving runtime security.

3. Avoid repeated loading

If a parent has already loaded a class, the child will not reload it, improving efficiency and saving memory.

4. Enable hierarchical isolation

Different loaders handle different layers (e.g., Extension vs. Application), creating a structured and extensible class‑loading hierarchy.

Custom class loaders

You can create your own loader by extending java.lang.ClassLoader and overriding findClass or loadClass. Overriding findClass keeps the parent‑delegation model; overriding loadClass breaks it.

Class loading process and principles

The lifecycle of a class consists of the following phases:

Loading

Linking

Initialization

Using

Unloading

During Loading , the JVM obtains the binary byte stream of the class (from a .class file, network, database, etc.), converts it into the method‑area data structures, and creates a java.lang.Class object.

Linking merges the loaded class into the runtime state and includes:

Verification : checks file format, metadata, bytecode correctness, and symbolic references.

Preparation : allocates memory for static variables and sets default values (e.g., int value becomes 0). Static final constants are assigned their actual values at this stage.

Resolution : converts symbolic references of classes, fields, and methods into direct memory references.

Initialization executes the class initializer ( <clinit>()), assigns static variable values, and runs static blocks.

Using means the class can now be instantiated, its methods invoked, etc.

Unloading occurs when the class loader is garbage‑collected and no instances or references to the java.lang.Class object remain.

public class ClassLoaderTest {
    public static void main(String[] args) {
        // Which class loader loaded ClassLoaderTest?
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println("Current class loader: " + classLoader);
        // Get the system (application) class loader
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("app: " + systemClassLoader);
        // Get the parent of the app class loader (extension class loader)
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println("ext: " + extClassLoader);
        // Get the parent of the extension class loader (bootstrap class loader)
        ClassLoader bootstrapClassloader = extClassLoader.getParent();
        System.out.println("boot: " + bootstrapClassloader);
    }
}
Current class loader: sun.misc.Launcher$AppClassLoader@18b4aac2
app: sun.misc.Launcher$AppClassLoader@18b4aac2
ext: sun.misc.Launcher$ExtClassLoader@4554617c
boot: null

The output shows that our class is loaded by the application class loader, while the bootstrap class loader (implemented in C++) is not directly accessible from Java and appears as null.

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.

JavaJVMclassloadercustom-loaderParent DelegationLoading Phases
Xuanwu Backend Tech Stack
Written by

Xuanwu Backend Tech Stack

Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.

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.