Classloader of JAVA class loading mechanism and the way to break the loading mechanism

At jdk1 In 8, the Classloader loads the bytecode of the class to the JVM. It follows the loading mechanism of the parental delegation model. It is mainly loaded by bootstrap Classloader, extclassloader, AppClassloader, and then the custom Classloader. This is mainly to ensure the internal class loading security of JDK, so the classes of the jar package of JDK's ClassPath are loaded first The parent delegation model is shown in the figure below. In fact, it is loaded by two levels of parent classloaders, bootstrap Classloader and extclassloader.

Parental delegation model of JDK

 

The following is the loadClass and core code of Classloader:

  1. First, the lock of class name is obtained to prevent multiple threads from loading a class concurrently, and only one thread loads it,
  2. findLoadedClass gets whether the Classloader instance in the cache loaded by the JVM has loaded the class. The bottom layer of the JVM is actually SystemDictionary, and its bottom layer is HASH table implementation.
  3. If this className has not been loaded in the JVM cache, it will be loaded by the parent loader first,

If the parent is empty, the bootstrap classloader will load it. Since bootstrap classloader is implemented by C + +, java does not have this class, so it calls the JVM function through JNI to load it. 4. If it has not been loaded, the findClass method implemented by the subclass will be loaded. 5. Finally, judge whether the resolve parameter handles the link of class. 6. Finally, class is returned. If it is not loaded, the subclass throws a ClassNotFoundException exception

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;
    }
}

Two ways JDK breaks the parental delegation model

Inherit ClassLoader and override loadClass

  1. Query whether the current Classloader has been loaded from the cache of the JVM layer.
  2. If you do not directly query whether there is a corresponding resource from the local resource, if so, you can directly call define to load it into the JVM and return Class
  3. If there are no local resources, the corresponding resources will be loaded from the Classloader of the parent
  4. Finally, return Class
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    System.out.println("load Class: " + name);
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if(c != null){
            return c;
        }
        byte[] bt = classNameToContent.get(name);
        if (bt != null) {
            try {
                return defineClass(name, bt, 0, bt.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        try {
           if (getParent() != null) {
              c = getParent().loadClass(name);
          }
        } catch (ClassNotFoundException e) {
            // ClassNotFoundException thrown if class not found
            // from the non-null parent class loader
        }
        if (c != null) {
            return c;
        }
    }
    return null;
}

contextClassLoader using Thread

An example of using the thread's contextClassloader to break the parental delegation model in JDK is serviceloader. Serviceloader is the full name of JDK, which is the Service Provider Interface. It is the way to implement the Service Provider Interface and plug-in. For example, the JDBC Driver uses the JDK SPI, and the specific implementation is to adapt to different databases, Since the Driver is loaded by bootstrap classloader in rt.jar and the interface implementation class is provided by a third-party jar package, there is no way for all bootstrap classloaders. Therefore, JDK makes a compromise and delegates it to the contextloader of the current thread to load the implementation class
The following is the load method of ServiceLoader. It can be seen that it is the implementation class of the interface loaded by the contextClassloader of the parent thread. The current main thread class loader is loaded by AppClassloader by default. This violates the parental delegation model

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

Parallel class loading mechanism

The loadClass method in the abstract class of Classloader was introduced earlier. At the beginning of loading, you need to obtain the lock of the full name of the class. If the parallel class loading mechanism is used, it will be used If you need to use parallel class loading mechanism, you can only add custom class loading to a static code block and add the following line.

static {
    ClassLoader.registerAsParallelCapable();
}

ClassLoader#registerAsParallelCapable

  1. Register the custom class that inherits ClassLoader into the Set collection of ParallelLoaders
protected static boolean registerAsParallelCapable() {
    Class<? extends ClassLoader> callerClass =
        Reflection.getCallerClass().asSubclass(ClassLoader.class);
    return ParallelLoaders.register(callerClass);
}

The ParallelLoaders class actually maintains a Set of classloader implementation classes, in which the elements are classloaders registered as parallel class loads by calling registerAsParallelCapable,

private static class ParallelLoaders {
    private ParallelLoaders() {}

    // the set of parallel capable loader types
    private static final Set<Class<? extends ClassLoader>> loaderTypes =
        Collections.newSetFromMap(
            new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
    static {
        synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); }
    }

    /**
     * Registers the given class loader type as parallel capabale.
     * Returns {@code true} is successfully registered; {@code false} if
     * loader's super class is not registered.
     */
    static boolean register(Class<? extends ClassLoader> c) {
        synchronized (loaderTypes) {
            if (loaderTypes.contains(c.getSuperclass())) {
                // register the class loader as parallel capable
                // if and only if all of its super classes are.
                // Note: given current classloading sequence, if
                // the immediate super class is parallel capable,
                // all the super classes higher up must be too.
                loaderTypes.add(c);
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * Returns {@code true} if the given class loader type is
     * registered as parallel capable.
     */
    static boolean isRegistered(Class<? extends ClassLoader> c) {
        synchronized (loaderTypes) {
            return loaderTypes.contains(c);
        }
    }
}

In the classloader constructor,

  1. If the Classloader has registered a parallel class loader, create a HashMap of the lock of the parallelLockMap,
private ClassLoader(Void unused, ClassLoader parent) {
    this.parent = parent;
    if (ParallelLoaders.isRegistered(this.getClass())) {
        parallelLockMap = new ConcurrentHashMap<>();
        package2certs = new ConcurrentHashMap<>();
        domains =
            Collections.synchronizedSet(new HashSet<ProtectionDomain>());
        assertionLock = new Object();
    } else {
        // no finer-grained lock; lock on the classloader instance
        parallelLockMap = null;
        package2certs = new Hashtable<>();
        domains = new HashSet<>();
        assertionLock = this;
    }
}

Classloaer#getClassLoadingLock

  1. When obtaining the class lock in loadClass, it will be judged that the parallelLockMap is not empty, an Object object will be created as the lock of this classloader class, and then put into the hashMap

In this way, the synchonized lock is not the this pointer of the Classloader instance, but the lock that obtains the full name of the loaded class in the Hashmap, so that different class full names can be loaded in parallel, which reduces the granularity of the lock and improves the speed of class loading

protected Object getClassLoadingLock(String className) {
    Object lock = this;
    if (parallelLockMap != null) {
        Object newLock = new Object();
        lock = parallelLockMap.putIfAbsent(className, newLock);
        if (lock == null) {
            lock = newLock;
        }
    }
    return lock;
}

summary
This paper mainly expounds the parent delegation model and two ways to break the parent delegation in JDK. Finally, it introduces the implementation principle of parallel class loading in JDK

 

Keywords: Java Back-end

Added by serg91 on Wed, 05 Jan 2022 23:50:23 +0200