Class Loading in Java: An In-Depth Exploration

Class Loading in Java: An In-Depth Exploration

The Java Development Kit (JDK) provides three default ClassLoaders, which play a crucial role in loading Java classes. These ClassLoaders are responsible for loading core Java class libraries, extension classes, and application-specific classes. In this article, we will delve into the world of ClassLoaders, exploring their hierarchical relationship and the delegation model that governs their behavior.

The Three Default ClassLoaders

  1. Bootstrap ClassLoader (Start Class Loader): Written in C++, this ClassLoader is responsible for loading core Java class libraries. The path for these libraries is specified by the % JAVA_HOME% / jre / lib parameter. Developers can also specify the path using the -Xbootclasspath parameter and the % JAVA_HOME% / jre / classes directory.
  2. Extension ClassLoader (extension class loader): This ClassLoader is responsible for loading extension classes that extend the JVM libraries. The path for these libraries is specified by the % JAVA_HOME% / jre / lib / ext directory and the java.ext.dirs system variable.
  3. Application ClassLoader (application loader): This ClassLoader is responsible for loading application-specific classes and is the default ClassLoader for Java programs. It loads classes from the specified classpath libraries.

Implementing Custom ClassLoaders

In addition to these three default ClassLoaders, developers can implement their own custom ClassLoaders according to their needs. This flexibility allows for the creation of specialized ClassLoaders that can load classes from specific locations or with specific characteristics.

The Hierarchical Model of ClassLoaders

The hierarchical relationship between ClassLoaders is typically as follows:

Bootstrap ClassLoader
  |
  |-- Extension ClassLoader
  |    |
  |    |-- Application ClassLoader
  |
  |-- Null (representing the starting ClassLoader)

This hierarchical relationship is demonstrated by the following code:

public class Test {
    public static void main(String args[]) {
        System.out.println(ClassLoader.getSystemClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
    }
}

AppClassLoader …xtClassLoader …Null

Note that the last value is null, representing the starting ClassLoader, which is written in C++ and cannot be obtained directly.

The Class Loader Delegation Model

The delegation model between ClassLoaders is as follows:

  • The top-level BootLoader Class is responsible for loading core Java class libraries.
  • The rest of the ClassLoaders load classes by delegating to their parent ClassLoader.
  • When a ClassLoader loads a class, it first requests the task from its parent ClassLoader.
  • If the parent ClassLoader can complete the task successfully, it returns the loaded class.
  • If the parent ClassLoader fails to load the class, the task is passed down to the next ClassLoader in the hierarchy.

The loadClass() Method

The loadClass() method in the java.lang.ClassLoader class demonstrates the delegation model:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // Check if class is loaded too
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // If the parent class is not empty then the parent class loader for delegation
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // empty parent class loader request to start
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // If the parent class throws an exception on behalf of the parent class can not be loaded
            }
            // parent class is not loaded
            if (c == null) {
                // use method to load itself findClass
                long t1 = System.nanoTime();
                c = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

In this article, we have explored the world of ClassLoaders in Java, including the three default ClassLoaders, the hierarchical relationship between ClassLoaders, and the delegation model that governs their behavior. We have also examined the loadClass() method in the java.lang.ClassLoader class, which demonstrates the delegation model.