Java ClassLoader

What Is a ClassLoader?

The JVM uses something called a Class Loader to load classes. There is not just one Class Loader. In addition to the multiple class loaders used by the JVM for different purposes, J2EE containers also define additional class loaders hierarchically, and users can define their own class loaders as well.

A Class Loader loads classes into memory. In other words, it dynamically loads compiled classes (.class) into memory. Dynamic loading means that when a class file is needed and executed at runtime, rather than compile time, it is loaded into memory.

Basic Behavior of ClassLoader

There are three class loaders defined in J2SE.

  • Bootstrap Class Loader
    • Initial class loader
    • Class loader for core packages and so on (rt.jar, i18n.jar)
  • Extension Class Loader
    • Class loader for installed optional extension packages (lib/ext)

    • Up to Java 8: inherits URLClassLoader and loads classes in jre/lib/ext and folders specified by the java.ext.dirs environment variable.

    • From Java 9 onward: no longer supports jre/lib/ext or java.ext.dirs, and loads all Java SE classes and classes of modules standardized by the JCP (Java Community Process).

      • It inherits BuiltinClassLoader, is implemented as an internal static class of ClassLoader, and has been changed to PlatformClassLoader.
  • User-defined Class Loader (System Class Loader, Application Class Loader)
    • classpath class loader

Class loaders have parent-child relationships. A parent class loader does not know its child class loaders, but a child class loader knows its parent. When a child class loader receives a class loading request, it delegates to the parent and loads the first class found. If the target class belongs to the responsibility range of a child class loader rather than the class loader that received the request, the class is not found and loading fails.

  1. A class loader receives a class loading request. loadClass()
  2. It checks whether the class is already loaded. findLoadedClass
  3. If it is not loaded, processing is delegated to the parent class loader, or to the bootstrap class loader (findBootstrapClassOrNull()) if there is no parent. parent.loadClass(name, false)
  4. If it is still not loaded, it searches for and tries to load it. findClass()

Specifically, the loadClass(..) method of java.lang.ClassLoader shows this behavior.

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

The bootstrap class loader loads core packages and similar classes, and it has no parent itself. A user-defined class loader is derived with the system class loader, which runs at boot time, as its parent.

For example, in a J2EE container, the class loader that loads an EAR utility JAR becomes the parent of the class loader that loads WAR web components, so web components can call the EAR utility JAR. Conversely, the EAR utility JAR cannot call web components inside the child WAR.

Also, fields qualified as static are executed only once when the class is loaded, so they exist as a single instance within the class loader. Integrating class loaders can make utility classes common classes, but it may cause unexpected side effects, so you need to understand and carefully inspect the behavior.

A class loader is a class that inherits from the abstract class java.lang.ClassLoader. For example, within core packages, java.security.SecureClassLoader and java.net.URLClassLoader are defined as classes that inherit from this class.

Example of Searching ClassLoaders

The following code is an example that searches class loaders.

package com.devkuma.basic.classloader;

class GetClassLoader {
    public static void main(String[] args) {
        // Bootstrap class loader
        System.out.println("- Bootstrap Class Loader");
        ClassLoader bootstrap = "".getClass().getClassLoader();
        System.out.println(bootstrap);
        if (bootstrap != null) {
            ClassLoader parent = bootstrap.getParent();
            System.out.println(parent);
        } else {
            System.out.println("No parent");
        }

        // Platform Class Loader
        System.out.println("- Platform Class Loader");
        ClassLoader extensions = GetClassLoader.class.getClassLoader().getParent();
        System.out.println(extensions);
        if (extensions != null) {
            ClassLoader parent = extensions.getParent();
            System.out.println(parent);
        } else {
            System.out.println("No parent");
        }

        // System Class Loader
        System.out.println("- System Class Loader");
        DemoClass myObj = new DemoClass();
        ClassLoader systems = myObj.getClass().getClassLoader();
        System.out.println(systems);
        if (systems != null) {
            ClassLoader parent = systems.getParent();
            System.out.println(parent);
        } else {
            System.out.println("No parent");
        }

        // Context Classloader
        System.out.println("- Context Classloader");
        ClassLoader threads = Thread.currentThread().getContextClassLoader();
        System.out.println(threads);
        if (threads != null) {
            ClassLoader parent = threads.getParent();
            System.out.println(parent);
        } else {
            System.out.println("No parent");
        }
    }
}

class DemoClass {
    String getClassName() {
        return "DemoClass";
    }
}

The output is as follows. Here, the context class loader is the class loader used to load classes within that thread.

- Bootstrap Class Loader
null
No parent
- Platform Class Loader
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7dc5e7b4
null
- System Class Loader
jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7dc5e7b4
- Context Classloader
jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7dc5e7b4