The Java Classloader, a part of the Java Runtime Environment, dynamically loads Java classes into the Java Virtual Machine. Each Java class must be loaded by a class loader.

When the JVM is started, three class loaders are used:

  • Bootstrap class loader
  • Extensions class loader
  • System class loader

The bootstrap class loader loads the core Java libraries located in the <JAVA_HOME>/jre/lib directory. This class loader, which is part of the core JVM, is written in native code.

The extensions class loader loads the code in the extensions directories (<JAVA_HOME>/jre/lib/ext, or any other directory specified by the java.ext.dirs system property). It is implemented by the sun.misc.Launcher$ExtClassLoader class.

The system class loader loads code found on java.class.path, which maps to the CLASSPATH environment variable. This is implemented by the sun.misc.Launcher$AppClassLoader class.

The following picture lists for conclusion.

We can verify that with code:

public class JavaTestApp {

    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("com.qiniu.fusion.app.Demo");
            ClassLoader classLoader = clazz.getClassLoader();
            System.out.printf("currentClassLoader is %s\n", classLoader.getClass().getSimpleName());
            while (classLoader.getParent() != null) {
                classLoader = classLoader.getParent();
                System.out.printf("Parent is %s\n", classLoader.getClass().getSimpleName());
            }
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

class Demo {
    static int count = 0;
}

The output is,

currentClassLoader is AppClassLoader
Parent is ExtClassLoader

The parent of AppClassLoader is ExtClassLoader, while ExtClassLoader.getParent() = null. BootstrapClassLoader is built-in of JVM.

Every Class object has a ClassLoader

Every Class object contains a getClassLoader() reference to the ClassLoader that defined it.

For example, this snippet of code outputs sun.misc.Launcher$AppClassLoader@42a57993

public class AppTest {

    public static void main(String[] args) {
        Demo d = new Demo();
        System.out.println(d.getClass().getClassLoader());
    }
}

class Demo {
    static int count = 0;
}

It arouses my interest to see what getClassLoader() does.

// Class.java

public ClassLoader getClassLoader() {
    ClassLoader cl = getClassLoader0();
    if (cl == null)
        return null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
    }
    return cl;
}

ClassLoader getClassLoader0() { return classLoader; }

private final ClassLoader classLoader;

private Class(ClassLoader loader) {
    classLoader = loader;
}

Each Class object includes a private field classLoader, which is initialized in JVM not by private constructor.

Parent Delegation Model

Allow me back to ClassLoader.

// ClassLoader.java

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;

    // The classes loaded by this class loader. The only purpose of this table
    // is to keep the classes from being GC'ed until the loader is GC'ed.
    private final Vector<Class<?>> classes = new Vector<>();
}

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader.

Let’s see how a Class object is loaded.

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
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

When requested to find a class or resource,

  1. First, check if the class has already been loaded.
  2. if parent exists, it will load class. otherwise, The bootstrap class loader tries load the class.
  3. If still not found, then invoke findClass in order to find the class.

loadClass determines the class(es) referred to, since the methods and constructors of objects created by a class loader may reference other classes.

defineClass

Normally, the Java virtual machine loads classes from the local file system in a platform-dependent manner. For example, on UNIX systems, the virtual machine loads classes from the directory defined by the CLASSPATH environment variable.

However, some classes may not originate from a file; they may originate from other sources, such as the network, or they could be constructed by an application. The method defineClass(String, byte[], int, int) converts an array of bytes into an instance of class Class.

Instances of this newly defined class can be created using Class#newInstance.

// ClassLoader.java

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

preDefineClass determines protection domain, and check that:

  • not define java.* class,
  • signer of this class matches signers for the rest of the classes in package.

Example

An example is necessary to illustrate the Parent Delegation Model.

Compile Demo class to jar, and move it to $JAVA_HOME/jre/lib/ext/.

javac classloader/Demo.java
jar cvf Demo.jar classloader/Demo.class
mv Demo.jar $JAVA_HOME/jre/lib/ext/

Then, loads Demo class with explicit AppClassLoader.

public class JavaTestApp {

    public static void main(String[] args) {
        try {
            ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); // AppClassLoader
            Class<?> clazz = appClassLoader.loadClass("com.qiniu.fusion.app.Demo");
            ClassLoader classLoader = clazz.getClassLoader();
            System.out.printf("ClassLoader is %s", classLoader.getClass().getSimpleName());
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

However, the outputs, currentClassLoader is ExtClassLoader, means Demo class is loaded with ExtClassLoader still.

Reference