preface

In the article, from the perspective of the following launched a comprehensive introduction to the class loader, the timing of the class loader is initialized, class loaders loading way, introduce a variety of a variety of class loaders, all kinds of class loader to load the file path, the initialization process of class loaders, see this friend should have a general knowledge of class loaders, So I ask two questions here:

  1. Why do different classloaders have different read classpath
  2. What exactly is in the parent property of each classloader, and why do we have this parent-child relationship

In fact, all of these questions can be answered by a single mechanism: the parent delegate mechanism, which is a feature of the class loader to load classes. This mechanism is implemented by Java level code, so in this article, we will focus on this mechanism

Parent delegation mechanism

Flowchart of parent delegation mechanism

If the parent loader fails to load, the subclass loader will load itself.

From the flow diagram, the application class loader will first delegate to the extension class loader, and when the extension class loader loads the class, it will delegate to the boot class loader (regardless of the custom class loader).

When the boot class loader fails to load, it will be handed over to the subclass extension class loader to try again, and when the extension class loader fails to load, it will be handed over to the subclass application class loader to load

On the whole, this is a bottom-up and then down such a process

Parent delegate mechanism source analysis

TestJDKClassLoader: parent delegate: parent delegate: parent delegate: parent delegate: parent delegate: parent delegate: parent delegate

public class TestJDKClassLoader {
    public static void main(String[] args) {
        system.out.println("Class loading......."); }}Copy the code

First of all, according to the previous article, the loader shown in the figure below is the AppClassLoader that we assign when initializing the Launcher

We call class loader in ordinary when loading a class usually calls loadClass in this method, so we from sun. Misc. The Launcher. AppClassLoader# loadClass as entrance, have a good look at the specific implementation

        publicClass<? > loadClass(String var1,boolean var2) throws ClassNotFoundException {
            // This is not the case.if (this.ucp.knownToNotExist(var1)) {
                // This is not the case. }else {
                    throw newClassNotFoundException(var1); }}else {
                // Finally the loadClass method of the parent class is called
                return super.loadClass(var1, var2); }}Copy the code

LoadClass method of the parent class

// The loadClass method of the classLoader implements the parent delegate mechanism
protectedClass<? > loadClass(String name,boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // Check whether the current class loader has already loaded the classClass<? > c = findLoadedClass(name);if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // If the parent of the current loader is not empty, delegate the parent class loader to load
                    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();
                    // Both 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; }}Copy the code

The parent delegate pattern is implemented in this loadClass method, which we’ll look at step by step

FindLoadedClass () {return null;} findLoadedClass () {return null;} So this must return null

    protected finalClass<? > findLoadedClass(String name) {if(! checkName(name))return null;
        return findLoadedClass0(name);
    }
    private native finalClass<? > findLoadedClass0(String name);Copy the code

The return value is null, indicating that the class has not been loaded, so the parent of the current class loader is determined to be empty. If the parent is not empty, the parent will be loaded

// If the parent of the current loader is not empty, delegate the parent class loader to load
if(parent ! =null) {
   c = parent.loadClass(name, false);
}
Copy the code

This is an AppClassLoader, and the parent is an ExtClassLoader, so this is definitely not empty, so calling the parent’s loadClass method is equivalent to delegating the AppClassLoader to the ExtClassLoader for loading

The parent attribute of the ExtClassLoader is null, so findBootstrapClassOrNull. The findBootstrapClassOrNull method is equivalent to delegating to the BootStrapLoader class loader

    // If the parent of the current loader is not empty, delegate the parent class loader to load
    if(parent ! =null) {
       c = parent.loadClass(name, false);
    } else {
       c = findBootstrapClassOrNull(name);
    }
    // Check whether the BootstrapClassLoader has loaded the class. If not, return null
    privateClass<? > findBootstrapClassOrNull(String name) {if(! checkName(name))return null;

        return findBootstrapClass(name);
    }
    // If not found, return null
    private nativeClass<? > findBootstrapClass(String name);Copy the code

The BootStrapLoader class loader will start loading, and it will go to the corresponding loading path, but the TestJDKClassLoader class is in our application, and the core JDK package is not there, so it will fail. After returning null, Again in the loadClass method of the ExtClassLoader class, the ExtClassLoader class accepts the BootStrapLoader delegate and then enters the code below

                if (c == null) {
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
Copy the code

The ExtClassLoader class loader accepts the BootStrapLoader delegate, and finding the findClass method in the lib\ext path is the implementation of finding it

The core part of findClass is the red-boxed part. First it spells out the class path (replace “.” with “/” and suffix class). Then based on the classpath, it starts looking for the classpath that the current classloader is responsible for. If not, return NULL

Obviously, ExtClassLoader will not load our application’s TestJDKClassLoader class and will return null

C = parent. LoadClass (name, false) is a nested method. It returns to the AppClassLoader class which is equivalent to the AppClassLoader accepting the ExtClassLoader delegate and executing the following code

                if (c == null) {
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
Copy the code

The AppClassLoader accepts the ExtClassLoader delegate, and finding the findClass method in the corresponding path is the implementation of finding

This method is very important. All it does is load the class, load -> validate -> prepare -> parse -> initialize. We are not going to follow it here.

At this point, we’ve completed a parent-delegate process that starts with AppClassLoader and delegates to ExtClassLoader and then delegates to BootStrapLoader to start loading, If the BootStrapLoader cannot be loaded and then delegated to ExtClassLoader, and the ExtClassLoader cannot load and then delegated to AppClassLoader, such a bottom-up and down process is also consistent with the flow chart again

Thinking about the process of parental delegation

Think about a

Why do class loaders delegate from the bottom up, rather than from the top down or some other order? Because if you load directly from top to bottom, you only have to go once, and if you load directly from bottom to top to bottom, you have to repeat, so is that necessary at all?

In fact, this ensures that the loading process for all classes remains uniform, starting from the AppClassLoader

Think about the second

Why does the JVM start loading from AppClassLoader? Why not just return to the BootStrapLoader class loader? Does this avoid an upward delegate process

In our everyday applications, more than 90% of the classes are in the classPath directory, and although the delegate cycle is repeated on the first load, if a class is repeated a second, third, fourth, and so on (manual load scenario), AppClassLoader determines that it will return if it has already loaded, avoiding delegating up

If you start with the BootStrapLoader class loader, you’ll need to go up and down no matter how many times you repeat it, and while the first time is faster, each time after that is actually slower

Advantages and disadvantages of the parent delegation mechanism

Having said that, let’s take a look at the pros and cons of this mechanism

advantages

  1. Sandbox security: Different classes are loaded by different classloaders to protect core Java classes from arbitrary modification

  2. Avoid class reloading: When the parent loader has already loaded the class, there is no need for the classLoader of the subclass to load the class again to ensure the uniqueness of the loaded class

Sandbox security scenario: Create a java.lang package under the application, create a new String class under the package, and run the main method in the String class

Analysis output results:

Because in the parent delegate mechanism, when we delegate up to the BootStrapLoader class loader, we find that java.lang.String is in the path it scans, so it will load (only the filename), but instead of loading our custom String class, it will load the String class from the RT package. Java’s own String class does not have a main method, so it will report an error, which protects the Java core library from being arbitrarily modified

disadvantages

The parent delegate mode of class loading is not suitable for all scenarios

Here’s an example: A distinct feature of parental delegation is that classes in the same path are only loaded once, but in Tomcat, if one Tomcat wants to deploy multiple applications that happen to depend on different smaller versions of Spring, such as Spring4.1x or Spring4.2x, The two tiny versions of Spring will certainly have classes in the same path, but if you use the parent delegate classloader, only one class in the same path will be loaded, and one application will use some of the features of the other class, so the application will not execute properly. Therefore, the parent delegation mechanism is definitely not applicable in the Tomcat scenario and must be circumvented

Custom class loaders

Since you can’t circumvent this parental delegation mechanism with an existing class loader, you need to customize the class loader first

Custom loaders simply inherit from the java.lang.ClassLoader class, which has two core methods

  • One is the loadClass method, which implements our parent delegate mechanism
  • There is also the findClass method, which is an empty implementation by default. The definition of this method is to find our class by path and load it. To break the parent delegate mechanism, we just need to override the loadClass method

Write a custom class loader

Let’s just override the findClass method to show why we have to override the loadClass method

public class MyClassLoaderTest {
    // Custom classloaders generally need to inherit a ClassLoader (there are many methods that can be reused)
    static class MyClassLoader extends ClassLoader {
        // Define the path where the class loader loads the class
        private String classpath;
        public MyClassLoader(String classpath) {
            this.classpath = classpath;
        }
        /** * Custom file reading method *@paramName File path name *@returnBinary data read *@throwsThe Exception of a * /
        private  byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\ \."."/");
            FileInputStream fileInputStream = new FileInputStream(classpath + "/" + name + ".class");
            int len = fileInputStream.available();
            byte[] data = new byte[len];
            fileInputStream.read(data);
            fileInputStream.close();
            return data;
        }

        @Override
        protectedClass<? > findClass(String name)throws ClassNotFoundException {
            try {
                // Customize the reading method of the class
                byte[] data = loadByte(name);
                // defineClass is just a class load, use native
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw newClassNotFoundException(); }}}public static void main(String[] args) throws Exception {
        // to initialize the custom ClassLoader, the parent ClassLoader is initialized first, which sets the parent of the custom ClassLoader to the application ClassLoader AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        / / D plate to create the test/this/TestJDKClassLoader TestJDKClassLoader. Drop the class into that directory
        Class clazz = classLoader.loadClass("classLoader.TestJDKClassLoader");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout".null);
        method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); }}Copy the code

Let’s look at the output:

I can fly
sun.misc.Launcher$AppClassLoader
Copy the code

The TestJDKClassLoader class is loaded by the AppClassLoader class loader, although we have written a custom class loader

The parent of a custom class loader

As mentioned in the previous article, every class has a parent property, so what does the parent property of a custom classloader store? Take a look at the source:

We found that when initializing the custom ClassLoader, since it inherits the ClassLoader, the constructor of the parent class is called first. In the constructor of the parent class, a system ClassLoader is plugged by default

If you go to the getSystemClassLoader method, it still calls launcher. getClassLoader, and this method returns AppClassLoader

That is, the parent property of the custom classloader is the AppClassLoader above

This analysis actually explains the above question:

  • The default parent of a custom classloader is AppClassLoader, and since the loadClass method is not overridden, that is, the parent delegate mechanism is not broken, so the parent delegate mode is still implemented
  • I didn’t put it in the projectTestJDKClassLoader.javaIf this class is removed, AppClassLoader will still be able to read it in the project pathThe compiled testjdkClassLoader.class file

Create testjdkClassLoader. class file in D:/test/classLoader

I can fly
classLoader.MyClassLoaderTest$MyClassLoader
Copy the code

The parent AppClassLoader cannot find the testjdkClassLoader. class file in the parent AppClassLoader’s path.

Break the parent delegate mechanism

In order to break the parent delegate mechanism, we still need to override the loadClass method. In the loadClass method, instead of delegating to the parent class, we try to load it directly from the current class loader, so let’s override the loadClass

public class MyClassLoaderTest {

    // Custom classloaders generally need to inherit a ClassLoader (there are many methods that can be reused)
    static class MyClassLoader extends ClassLoader {
        private String classpath;
        public MyClassLoader(String classpath) {
            this.classpath = classpath;
        }
        /** * Custom file reading method *@paramName File path name *@returnBinary data read *@throwsThe Exception of a * /
        private  byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\ \."."/");
            FileInputStream fileInputStream = new FileInputStream(classpath + "/" + name + ".class");
            int len = fileInputStream.available();
            byte[] data = new byte[len];
            fileInputStream.read(data);
            fileInputStream.close();
            return data;
        }

        @Override
        protectedClass<? > loadClass(String name,boolean resolve) throws ClassNotFoundException {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loadedClass<? > c = findLoadedClass(name);if (c == null) {
                    // Find that if the current class loader is not loaded, then load it
                    c = findClass(name);
                }
                if (resolve) {
                    resolveClass(c);
                }
                returnc; }}@Override
        protectedClass<? > findClass(String name)throws ClassNotFoundException {
            try {
                // Customize the reading method of the class
                byte[] data = loadByte(name);
                // defineClass is just a class load, use native
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw newClassNotFoundException(); }}}public static void main(String[] args) throws Exception {
        // to initialize the custom ClassLoader, the parent ClassLoader is initialized first, which sets the parent of the custom ClassLoader to the application ClassLoader AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        / / D plate to create the test/this/TestJDKClassLoader TestJDKClassLoader. Drop the class into that directory
        Class clazz = classLoader.loadClass("classLoader.TestJDKClassLoader");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout".null);
        method.setAccessible(true);
        method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); }}Copy the code

After running the application, the error in the figure below is reported

  • On the one hand, because in Java, every class inherits Object by default, the object. class file does not exist in the test/classLoader directory

  • On the other hand, Java does not allow core packages to be loaded using custom class loaders. If you do not believe that you can drag Object

Filter special classes

To allow the Object class to load, we also need to do a simple filter in loadClass to ensure that certain classes (like Object) still use the parent delegate mechanism, and our application’s classes break the parent delegate mechanism:

        @Override
        protectedClass<? > loadClass(String name,boolean resolve) throws ClassNotFoundException {
            synchronized(getClassLoadingLock(name)) { Class<? > c = findLoadedClass(name);if (c == null) {
                    // Break the parent delegate mechanism if it is a file in the specified directory
                    if (name.startsWith("classLoader")) {
                        c = findClass(name);
                    } else {
                        // The parent delegate mechanism is used
                        c = this.getParent().loadClass(name); }}if (resolve) {
                    resolveClass(c);
                }
                returnc; }}Copy the code

Let’s run it:

I can fly
classLoader.MyClassLoaderTest$MyClassLoader
Copy the code

Can see even exist in the project. This MyClassLoaderTest class, or to use its own class loader loads, useless AppClassLoader of the parent class loader, prove that have broken parents delegate mechanism

A scenario that breaks the parental delegation mechanism

There are a number of scenarios where parental delegation is broken, and Tomcat is a prime example

  1. Tomcat may have multiple applications deployed, and different applications may rely on different versions of the same third-party class library (resulting in a large number of classes with the same file path). In this case, the parent delegate mechanism cannot be used to load classes. Ensure that the class libraries of each application are independent and isolated from each other

  2. The Web container needs to support JSP modification, and JSP files eventually need to be compiled into class files to run in the virtual machine, but JSP modification after the program runs is a frequent thing, and the Web container needs to support JSP modification without restarting

  3. . There are many more scenarios that you need to understand for yourself

This paper summarizes

Ok, that’s all the content of this article, which can be roughly divided into the following sections

  1. Combined with the source code to analyze the process of parental delegation mechanism
  2. Talk about the implications of parental delegation and its pros and cons
  3. How do I customize class loaders? What do I need to pay attention to
  4. How to break parental delegation

Seeing this, I believe you can answer the first two questions:

  • Why do different classloaders have different read classpath? Having different classes loaded by different classloaders protects the Java core classes from arbitrary modification

  • What exactly is in the parent property of each classloader, and why do we have this parent-child relationship? The purpose of establishing parent-child relationship is to realize the mechanism of parental delegation, which can see its advantages

This is the end of class loading, but it is not the end of class loading, Tomcat series of articles, one of which is the application of a custom class loader in Tomcat, here is not suitable for detailed expansion, friends can look forward to

The purpose of class loading is to load a class’s bytecode file into the JVM runtime data area and turn it into class metadata that can be used directly, but the JVM runtime data area stores more than class metadata, right? So the next article will examine how and where an object enters the JVM runtime data area.

omg

Finally, if you feel confused, please leave a comment in the first time, if you think I have something to ask for a thumbs up 👍 for attention ❤️ for share 👥 for me really very useful!! If you want to obtain the ebook “In-depth Understanding of Java Virtual Machine: ADVANCED features and Best Practices of JVM (3rd edition) Zhou Zhiming”, you can pay attention to wechat public account Java Encyclopedia, finally, thank you for your support!!