Detailed explanation of the parent delegation mechanism of class loader and class

Class loader

What is a class loader

The virtual machine design team puts the action of "obtaining a binary byte stream describing this class through the fully qualified name of a class" in the class loading stage outside the Java virtual machine to allow the application to decide how to obtain all The required class. The code module that implements this action is called a "class loader".

Class loader hierarchy

image-20200827154815302

The parent delegation model requires that except for the top-level startup class loader, all other class loaders should have their own parent class loaders. However, the parent-child relationship between class loaders here is generally not realized in an inheritance relationship, but a composition relationship is usually used to reuse the code of the parent loader.

From the perspective of the Java virtual machine, there are only two different class loaders: one is the Bootstrap ClassLoader (Bootstrap ClassLoader), which is implemented in C++ language and is part of the virtual machine itself; the other It is all other class loaders, these class loaders are implemented by the Java language, independent of the virtual machine, and all inherit from the abstract class java.lang.ClassLoader.

From a Java developer's point of view, class loaders can be divided in more detail. Most Java programs use class loaders provided by the following three systems:

Bootstrap ClassLoader (Bootstrap ClassLoader)

This class will be stored in the <JAVA_HOME>\lib directory, or in the path specified by the -Xbootclasspath parameter, and recognized by the virtual machine (identified by the file name, such as rt.jar, tools.jar, The class library whose name does not match will not be loaded even if it is placed in the lib directory) The class library is loaded into the virtual machine's memory.

Extension ClassLoader

This loader is implemented by sun.misc.Launcher$ExtClassLoader, which is responsible for loading all class libraries in the <JAVA_HOME>\lib\ext directory or in the path specified by the java.ext.dirs system variable. Developers can directly Use extended class loader.

Application ClassLoader

This class loader is implemented by sun.misc.Launcher$AppClassLoader. Since the application class loader is the return value of the getSystem-ClassLoader() method in the ClassLoader class, it is also called the "system class loader" in some occasions.

It is responsible for loading all class libraries on the user class path (ClassPath), and developers can also use this class loader directly in the code. If you have not customized your own class loader in the application, this is generally the default class loader in the program.

Our applications are loaded by these three types of loader in cooperation with each other, if necessary, you can also add your own class loader.

Three ways of class loading

  1. Initially loaded by JVM when the application is started from the command line
  2. Load dynamically through the Class.forName() method
  3. Load dynamically through the ClassLoader.loadClass() method

Code example:

public class loaderTest {
    
     
        public static void main(String[] args) throws ClassNotFoundException {
    
     
                ClassLoader loader = HelloWorld.class.getClassLoader(); 
                System.out.println(loader); 
                //使用ClassLoader.loadClass()来加载类,不会执行初始化块 
                loader.loadClass("Test2"); 
                //使用Class.forName()来加载类,默认会执行初始化块 
//                Class.forName("Test2"); 
                //使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块 
//                Class.forName("Test2", false, loader); 
        } 
}

public class Test2 {
    
     
        static {
    
     
                System.out.println("静态初始化块执行了!"); 
        } 
}

Class.forName()和ClassLoader.loadClass()区别

  • Class.forName(): Load the .class file of the class outside of the jvm, explain the class, and execute the static block in the class;

  • ClassLoader.loadClass(): Only one thing is to load the .class file into the jvm, the content in the static will not be executed, and the static block will be executed only in the newInstance.

  • Class.forName(name, initialize, loader) function with parameters can also control whether to load the static block. And only when the newInstance() method is called, the constructor is called to create the object of the class.

JVM class loading mechanism

Fully responsible

When a class loader is responsible for loading a Class, other classes that the Class depends on and referenced will also be loaded by the class loader, unless another class loader is used to load it.

Parent delegate

Let the parent class loader try to load the class first, and only try to load the class from its own class path when the parent class loader cannot load the class.

Caching mechanism

The caching mechanism will ensure that all loaded classes will be cached. When a class needs to be used in the program, the class loader first looks for the class from the cache area, and only the cache area does not exist, the system will read the corresponding class Binary data is converted into Class objects and stored in the buffer area. This is why after modifying the Class, the JVM must be restarted for the program modification to take effect.

Parental delegation mechanism

The working process of the parent delegation model is: if a class loader receives a request for class loading, it will not first try to load the class by itself, but will delegate the request to the parent class loader to complete it. Each level of class Loaders are like this, so all loading requests should eventually be transmitted to the top-level startup class loader, only when the parent loader reports that it cannot complete the loading request (the required class is not found in its search range) At that time, the child loader will try to complete the loading by itself.

Examples are as follows:

  1. When AppClassLoader loads a class, it does not first try to load the class by itself, but delegates the class loading request to the parent class loader ExtClassLoader to complete.
  2. When ExtClassLoader loads a class, it will not try to load the class by itself, but delegates the class loading request to BootStrapClassLoader to complete.
  3. If BootStrapClassLoader fails to load (for example, the class is not found in $JAVA_HOME/jre/lib), ExtClassLoader will be used to try to load;
  4. If ExtClassLoader also fails to load, it will use AppClassLoader to load, if AppClassLoader also fails to load, it will report an exception ClassNotFoundException.

Parent delegation mechanism

image-20200827135115768

The hierarchical relationship between class loaders shown in the figure above is called the Parents Delegation Model of the class loaders. The parent delegation model requires that in addition to the top-level startup class loader, all other class loaders should have their own parent class loaders. Here, the parent-child relationship between class loaders is generally not realized in the inheritance relationship, but the composition relationship is used to reuse the code of the parent loader .

The parent delegation model of the class loader was introduced during JDK 1.2 and was widely used in almost all Java programs afterwards, but it is not a mandatory constraint model, but a class recommended by Java designers to developers Loader implementation.

The working process of the parent delegation model is: if a class loader receives a request for class loading, it will not first try to load the class by itself, but will delegate the request to the parent class loader to complete it. Each level of class Loaders are like this, so all loading requests should eventually be transmitted to the top-level startup class loader, only when the parent loader reports that it cannot complete the loading request (the required class is not found in its search range) At that time, the child loader will try to complete the loading by itself.

Using the parent delegation model to organize the relationship between class loaders has an obvious advantage that Java classes have a priority hierarchical relationship along with its class loaders. For example, the class java.lang.Object, which is stored in rt.jar, no matter which class loader wants to load this class, it is ultimately delegated to the startup class loader at the top of the model to load, so the Object class is in the program All of the various class loader environments are the same class. On the contrary, if the parent delegation model is not used and each class loader loads it by itself, if the user writes a class called java.lang.Object and puts it in the ClassPath of the program, there will be more With a different Object class, the most basic behavior in the Java type system cannot be guaranteed, and the application will become chaotic.

Advantages of Parental Delegation

  • System class prevents multiple copies of the same bytecode in memory
  • Ensure the safe and stable operation of Java programs

image-20200827135310660

Parent delegation code implementation

public Class<?> loadClass(String name)throws ClassNotFoundException {
    
    
            return loadClass(name, false);
    }
    protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
    
    
            // 首先判断该类型是否已经被加载
            Class c = findLoadedClass(name);
            if (c == null) {
    
    
                //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
                try {
    
    
                    if (parent != null) {
    
    
                         //如果存在父类加载器,就委派给父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
    
    
                    //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
                        c = findBootstrapClass0(name);
                    }
                } catch (ClassNotFoundException e) {
    
    
                 // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                    c = findClass(name);
                }
            }
            if (resolve) {
    
    
                resolveClass(c);
            }
            return c;
        }
  

The logic of this code is clear and easy to understand: first check whether the requested type has been loaded, if not, call the loadClass() method of the parent loader, if the parent loader is empty, the startup class loader is used as the parent load by default Device. If the parent class loader fails to load and throws ClassNotFoundException, then it calls its own findClass() method to try to load.

Custom class loader

Normally, we use the system class loader directly. However, sometimes, we also need to customize the class loader. For example, the application transmits the bytecodes of Java classes through the network. To ensure security, these bytecodes are encrypted. At this time, the system class loader cannot load them, so a custom class loader is required. achieve. Custom class loaders generally inherit from the ClassLoader class. From the above analysis of the loadClass method, we only need to rewrite the findClass method.

Below we use an example to demonstrate the process of custom class loader:

The core of the custom class loader is to obtain bytecode files. If it is an encrypted bytecode, the file needs to be decrypted in this class. Since this is just a demonstration, I did not encrypt the class file, so there is no decryption process.

public class MyClassLoader extends ClassLoader {
    
    

    private String root;

    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        byte[] classData = loadClassData(name);
        if (classData == null) {
    
    
            throw new ClassNotFoundException();
        } else {
    
    
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
    
    
        String fileName = root + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
        try {
    
    
            InputStream ins = new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = ins.read(buffer)) != -1) {
    
    
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

    public String getRoot() {
    
    
        return root;
    }

    public void setRoot(String root) {
    
    
        this.root = root;
    }

    public static void main(String[] args)  {
    
    

        MyClassLoader classLoader = new MyClassLoader();
        classLoader.setRoot("D:\\temp");

        Class<?> testClass = null;
        try {
    
    
            testClass = classLoader.loadClass("com.pdai.jvm.classloader.Test2");
            Object object = testClass.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (InstantiationException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        }
    }
}
  

There are a few things to note here :

  1. The file name passed here needs to be the fully qualified name of the class, that is, the com.pdai.jvm.classloader.Test2format, because the defineClass method is processed in this format.
  2. It is best not to override the loadClass method, because it is easy to break the parent delegation model.
  3. This type of Test class itself can be loaded by the AppClassLoader class, so we cannot put com/pdai/jvm/classloader/Test2.class in the classpath. Otherwise, due to the existence of the parent delegation mechanism, this class will be directly loaded by AppClassLoader instead of our custom class loader.

Guess you like

Origin blog.csdn.net/kaihuishang666/article/details/108262819