Class loaders in Java

  • Bootstrap the classloader: is responsible for loading the core libraries that are located in the JRE’s lib directory, for examplert.jar,charsets.jarEtc.
  • Extension class loader: a class responsible for loading jar packages in the Ext extension directory in the JRE’s lib directory
  • Application class loader: Responsible for loadingClassPathThe class package under the path is mainly to load the classes written by the developer
  • Custom loader: loads class packages in user-defined paths

The following code will print out the class loader for each class in each of the above cases

package org.laugen.jvm;

import sun.misc.Launcher;
import java.net.URL;

public class JdkClassLoader {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(JdkClassLoader.class.getClassLoader().getClass().getName());
        System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassloader = appClassLoader.getParent();
        ClassLoader bootstrapLoader = extClassloader.getParent();
        System.out.println("the bootstrapLoader : " + bootstrapLoader);
        System.out.println("the extClassloader : " + extClassloader);
        System.out.println("the appClassLoader : " + appClassLoader);
        System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
        System.out.println("BootstrapLoader loads the following files:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i]);
        }
        System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
        System.out.println("ExtClassloader loads the following files:");
        System.out.println(System.getProperty("java.ext.dirs"));
        System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
        System.out.println("AppClassLoader loads the following files:");
        System.out.println(System.getProperty("java.class.path")); }}Copy the code

The result is as follows:

    null
    sun.misc.Launcher$ExtClassLoader
    sun.misc.Launcher$AppClassLoader
    =============================================
    the bootstrapLoader : nullthe extClassloader : sun.misc.Launcher$ExtClassLoader@4b67cf4d the appClassLoader : Sun. Misc. The Launcher $AppClassLoader @ 18 b4aac2 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = bootstrapLoader loading the following files:  file:/D:/Java/jdk18.. 0 _191/jre/lib/resources.jar
    file:/D:/Java/jdk18.. 0 _191/jre/lib/rt.jar
    file:/D:/Java/jdk18.. 0 _191/jre/lib/sunrsasign.jar
    file:/D:/Java/jdk18.. 0 _191/jre/lib/jsse.jar
    file:/D:/Java/jdk18.. 0 _191/jre/lib/jce.jar
    file:/D:/Java/jdk18.. 0 _191/jre/lib/charsets.jar
    file:/D:/Java/jdk18.. 0 _191/jre/lib/jfr.jar
    file:/D:/Java/jdk18.. 0 _191/ jre/classes = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = extClassloader loading the following files: D: \ Java \ jdk18.. 0 _191\jre\lib\ext; C: \ WINDOWS \ Sun \ Java \ lib \ ext = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = appClassLoader loading the following files: D: \ Java \ jdk18.. 0 _191\jre\lib\charsets.jar; D:\Java\jdk18.. 0 _191\jre\lib\deploy.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\access-bridge-64.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\cldrdata.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\dnsns.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\jaccess.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\jfxrt.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\localedata.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\nashorn.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\sunec.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\sunjce_provider.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\sunmscapi.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\sunpkcs11.jar; D:\Java\jdk18.. 0 _191\jre\lib\ext\zipfs.jar; D:\Java\jdk18.. 0 _191\jre\lib\javaws.jar; D:\Java\jdk18.. 0 _191\jre\lib\jce.jar; D:\Java\jdk18.. 0 _191\jre\lib\jfr.jar; D:\Java\jdk18.. 0 _191\jre\lib\jfxswt.jar; D:\Java\jdk18.. 0 _191\jre\lib\jsse.jar; D:\Java\jdk18.. 0 _191\jre\lib\management-agent.jar; D:\Java\jdk18.. 0 _191\jre\lib\plugin.jar; D:\Java\jdk18.. 0 _191\jre\lib\resources.jar; D:\Java\jdk18.. 0 _191\jre\lib\rt.jar; E:\learning\boat\jvm\code\jvm\target\classes; D:\JetBrains\IntelliJ IDEA2020.3\lib\idea_rt.jar
Copy the code

Note: since the instance of the BootstrapClassLoader was created by C++, null is printed here

Class loader initialization process

When you create the JVM Launcher instance Sun.misc.Launcher, inside the constructor Launcher(), you create the extended class loader ExtClassLoader and the application class loader AppClassLoader

By default, the JVM calls the class loader returned by the getClassLoader() method of the Launcher to load the class we wrote, and it returns the AppClassLoader

public Launcher(a) {
    Launcher.ExtClassLoader var1;
    try {
        // Construct the extended class loader with its parent set to null
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        // Construct the application class loader with parent set to ExtClassLoader
        // The loader property value of the Launcher is AppClassLoader, which is used to load our classes
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    // omit the code......
}
Copy the code

Parent delegation mechanism

This is the core of the parent delegate mechanism, as shown in the figure. In a nutshell, the parent loader loads first, and the child loader loads itself.

When we call the loadClass method of AppClassLoader, we actually call the loadClass method of its parent class ClassLoader. The general process of this method is as follows:

  1. Check to see if the class has been loaded, and if so, return directly
  2. If the class has not been loaded, determine whether the current loader is loaded by the parent loader, if so, by the parent loader, if not, by the boot class loader
  3. If neither the parent nor the boot class loader can find the specified class, the current class loader’sfindClassMethod completes class loading
protectedClass<? > loadClass(String name,boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // Check to see if the class is already loadedClass<? > c = findLoadedClass(name);if (c == null) {
            long t0 = System.nanoTime();
            try {
                if(parent ! =null) {
                    // If the parent loader of the current loader is not empty, delegate the parent loader to load the class
                    c = parent.loadClass(name, false);
                } else {
                    // If the parent of the current loader is empty, the boot class loader is delegated to load the classc = 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();
                // Call URLClassLoader's findClass method to find and load the class in the classpath of the loader
                c = findClass(name);

                // this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); }}if (resolve) {
            resolveClass(c);
        }
        returnc; }}protectedClass<? > findClass(final String name) throws ClassNotFoundException {
    finalClass<? > result;try {
        result = AccessController.doPrivileged(
            newPrivilegedExceptionAction<Class<? > > () {publicClass<? > run()throws ClassNotFoundException {
                    String path = name.replace('. '.'/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if(res ! =null) {
                        try {
                            // The defineClass method performs a series of classloading procedures
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw newClassNotFoundException(name, e); }}else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}
Copy the code

Why design parental delegation?

  • Sandbox security: Users cannot implement classes in the core library themselves to prevent the core API library from being tampered with
  • Avoid class reloading: When the parent loader has already loaded the class, there is no need for the child loader to load it again, ensuring the uniqueness of the loaded class

Take full responsibility for the delegation mechanism

Full responsibility means that when a ClassLoader is installed on a class, the classes that that class depends on and references are loaded by default by that ClassLoader unless another ClassLoader is explicitly used

Custom class loaders

A custom ClassLoader needs to inherit the java.lang.ClassLoader class, which has two core methods: loadClass, which implements parental delegation, and findClass, which implements an empty method by default. So our custom classloader basically overrides the findClass method.

package org.laugen.jvm;

public class Note {
    static {
        System.out.println("Org.laugen.jvm. Note class loaded");
    }
    
    public Note(a) {
        System.out.println("Created instance of org.laugen.jvm.Note class");
    }
    
    public void print(a) {
        System.out.println("This is a note"); }}Copy the code
package org.laugen.jvm;

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class TestCustomizeClassLoader {
    static class CustomizeClassLoader extends ClassLoader {
        private String classPath;

        public CustomizeClassLoader(String classPath) {
            this.classPath = classPath;
        }

        // Read the class bytecode file
        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\ \."."/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        @Override
        protectedClass<? > findClass(String name)throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw newClassNotFoundException(); }}}public static void main(String args[]) throws Exception {
        CustomizeClassLoader classLoader = new CustomizeClassLoader("D:/MyClasses");
        System.out.println("Parent loader of custom class loader:" + classLoader.getParent().getClass().getName());
        Class clazz = classLoader.loadClass("org.laugen.jvm.Note");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("print".null);
        // Call the Note class's print method
        method.invoke(obj, null);
        System.out.println("The classloader for the Note class is:"+ clazz.getClassLoader().getClass().getName()); }}Copy the code

The result is as follows:

The org.laugen.jvm.Note class is loaded by the $AppClassLoader. It creates an instance of the org.laugen.jvm.Note class. org.laugen.jvm.TestCustomizeClassLoader$CustomizeClassLoaderCopy the code

Break the parent delegate mechanism

The core implementation of the parent delegate mechanism is in the loadClass method, so to break the parent delegate mechanism, you need to rewrite the loadClass method

@Override protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<? > c = findLoadedClass(name); if (c == null) { long t1 = System.nanoTime(); If (name.startswith (" org.laugen.jvm.note ")) {// Note = findClass(name); } else {// Since the Note class needs to be loaded with Object, since the whole delegate design, we will use our own custom loader to load // therefore, for these classes, C = this.getparent ().loadClass(name); c = this.getparent (). } PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; }}Copy the code

So, what designs have you seen that break the parental delegation mechanism?