Virtual machine class loading mechanism - class loader

The Java virtual machine design team intends to implement the action of "obtaining the binary byte stream describing a class through the fully qualified name of a class" in the class loading stage outside the Java virtual machine, so that the application can decide how to obtain the required class by itself. The code that implements this action is called a "Class Loader".

Class and class loader

Although the class loader is only used to implement the loading action of classes, its role in Java programs is far beyond the class loading stage. For any class, the class loader that loads it and the class itself must jointly establish its uniqueness in the Java virtual machine. Each class loader has an independent class namespace.

The following example

/**
 * Demonstration of class loader and instanceof keyword
 *
 * @author zzm
 */
public class ClassLoaderTest {
​
    public static void main(String[] args) throws Exception {
​
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
​
        Object obj = myLoader.loadClass("jvm.chapter7.ClassLoaderTest").newInstance();
​
        System.out.println(obj.getClass());
        System.out.println(obj instanceof ClassLoaderTest);
    }
}

Print results

class jvm.chapter7.ClassLoaderTest
false

In the two lines of output results, you can see from the first line that this object is indeed a JVM Class chapter7. ClassLoaderTest is instantiated, but in the output of the second line, it is found that this object and Class JVM chapter7. ClassLoaderTest returned false when checking its type. This is because there are two ClassLoaderTest classes in the Java virtual machine at the same time. One is loaded by the application Class loader of the virtual machine, and the other is loaded by our custom Class loader. Although they all come from the same Class file, they are still two independent classes in the Java virtual machine, When checking the type of object, the result is naturally false.

Parental delegation mechanism

From the perspective of Java virtual machine, there are only two different class loaders: one is Bootstrap ClassLoader, which is implemented in C + + language and is a part of the virtual machine itself; The other is all other class loaders. These class loaders are implemented by the Java language, exist independently outside the virtual machine, and all inherit from the abstract class java lang.ClassLoader.

From the perspective of Java developers, class loaders should be divided in more detail. From jdk1 Since 2, Java has maintained the class loading architecture of three-tier class loader and parent delegation. Although this architecture has made some adjustments and changes after the emergence of Java modular system, it still hasn't changed its main structure.

For Java applications of JDK8 and earlier versions, most Java programs will use the class loader provided by the following three systems to load.

  • Bootstrap Class Loader: this class loader is responsible for loading and storing in < Java_ Home > \ lib directory, or stored in the path specified by the - Xbootclasspath parameter and recognized by the Java virtual machine (identified by file name, such as rt.jar and tools.jar, and the class libraries with inconsistent names will not be loaded even if placed in the Lib directory). The class libraries are loaded into the memory of the virtual machine. The boot class loader cannot be directly referenced by the Java program. When writing a custom class loader, if you need to delegate the loading request to the boot class loader for processing, you can directly use null instead. The code listing 7-9 shows Java lang.ClassLoader. The code fragment of getclassloader () method, in which the comments and code implementation clearly explain the Convention rules of using null value to represent the boot class loader

/**
     * Returns the class loader for the class.  Some implementations may use
     * null to represent the bootstrap class loader. This method will return
     * null in such implementations if this class was loaded by the bootstrap
     * class loader.
     *
     * <p> If a security manager is present, and the caller's class loader is
     * not null and the caller's class loader is not the same as or an ancestor of
     * the class loader for the class whose class loader is requested, then
     * this method calls the security manager's {@code checkPermission}
     * method with a {@code RuntimePermission("getClassLoader")}
     * permission to ensure it's ok to access the class loader for the class.
     *
     * <p>If this object
     * represents a primitive type or void, null is returned.
     *
     * @return  the class loader that loaded the class or interface
     *          represented by this object.
     * @throws SecurityException
     *    if a security manager exists and its
     *    {@code checkPermission} method denies
     *    access to the class loader for the class.
     * @see java.lang.ClassLoader
     * @see SecurityManager#checkPermission
     * @see java.lang.RuntimePermission
     */
@CallerSensitive
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;
}

  • Extension Class Loader: this Class loader is in Class sun misc. It is implemented in the form of Java code in launcher $extclassloader. It is responsible for loading < Java_ Home > \ lib \ ext directory, or by Java All Class libraries in the path specified by the ext.dirs system variable. According to the name of "extended Class loader", it can be inferred that this is an extension mechanism of Java system Class library. The development team of JDK allows users to place generic Class libraries in the EXT directory to extend the functions of JavaSE. After JDK9, this extension mechanism is replaced by the natural extension ability brought by modularization. Since the Extension Class Loader is implemented by java code, developers can directly use the Extension Class Loader to load Class files in the program.

  • Application Class Loader: this class loader is provided by sun misc. Launcher $appclassloader. Because the Application Class Loader is the return value of the getsystem ClassLoader () method in the ClassLoader class, it is also called "system class loader" in some cases. It is responsible for loading all class libraries on the user's ClassPath. Developers can also use this ClassLoader directly in their code. If the application has not customized its own class loader, it is generally the default class loader in the program.

Java applications before JDK9 are loaded by the cooperation of these three kinds of loaders. If users think it necessary, they can also add custom Class loaders to expand, such as adding Class file sources other than disk locations, Or realize Class isolation through Class loader (for example, tomcat's application Class loader is a custom implementation, which is used to isolate different applications), overloading and other functions. The cooperative relationship between these Class loaders is "usually" as shown in Figure 7-2.

The hierarchical relationship between various class loaders shown in Figure 7-2 is called the "Parents Delegation Model" of class loader. The parent delegation model requires that all class loaders except the top-level startup class loader should have their own parent class loader. However, the parent-child relationship between class loaders here is generally not implemented by Inheritance, but usually uses Composition relationship to reuse the code of the parent loader.

The working process of the parent delegation model is: if a class loader receives a class loading request, it will not try to load the class by itself, but delegate the request to the parent class loader to complete it. This is true for class loaders at every level, 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 load request (it does not find the required class in its search scope), the child loader will try to complete the load by itself.

One obvious advantage of using the parental delegation model to organize the relationship between class loaders is that classes in java have a hierarchical relationship with priority along with their class loaders. For example, class java Lang.Object, which is stored in rt.jar. No matter which class loader wants to load this class, it is finally delegated to the startup class loader at the top of the model for loading. Therefore, the object class can be guaranteed to be the same class in various class loader environments of the program. On the contrary, if the two parent delegation model is not used, and each class loader loads it by itself, if the user also writes a program called Java Lang. object class and put it in the ClassPath of the program, then many different object classes will appear in the system, the most basic behavior in the Java type system can not be guaranteed, and the application will become a mess. If readers are interested, they can try to write a Java class with the same name as the existing class in rt.jar class library. They will find that it can be compiled normally, but it can never be loaded and run.

Parental delegation model is very important to ensure the stable operation of Java programs, but its implementation is extremely simple. The code used to realize parental delegation is only more than ten lines, all concentrated in Java In the loadClass() method of lang. classloader.

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

The logic of this code is clear and easy to understand: first check whether the type requested to load 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 loader by default. If the parent class loader fails to load and throws a ClassNotFoundException exception, it will call its own findClass() method to try to load.

Custom class loader

All user-defined class loaders should inherit the ClassLoader class. In the subclass of user-defined ClassLoader, we usually have two methods:

  • Rewrite the loadClass method (this is where the parent delegation logic is implemented. Modifying it will destroy the parent delegation mechanism, which is not recommended)

  • Override findClass method (recommended)

Simple example of custom class loader:

public class MyClassLoader extends ClassLoader {
​
    //1. Define the path of bytecode file
    private String codePath;
​
    //2. Define the construction method
    public MyClassLoader(ClassLoader parent, String codePath) {
        super(parent);
        this.codePath = codePath;
    }
​
    public MyClassLoader(String codePath) {
        this.codePath = codePath;
    }
​
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //Declare input stream
        BufferedInputStream bis = null;
        //Declare output stream
        ByteArrayOutputStream baos = null;
        //Bytecode path
        try {
            String file =  codePath+name+".class";
            //Initialize input stream
            bis = new BufferedInputStream(new FileInputStream(file));
            //Initialize output stream
            baos = new ByteArrayOutputStream();
            //io read / write operation
            int len;
            byte[] data = new byte[1024];
            while ((len = bis.read(data)) != -1){
                baos.write(data,0,len);
            }
            //Gets the byte array in memory
            byte[] bytes = baos.toByteArray();
            //Call defineClass to convert byte array into Class instance
            Class<?> clazz = defineClass(null, bytes, 0, bytes.length);
            //Return class object
            return  clazz;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

use

public class ClassLoaderTest {
    public static void main(String[] args) {
        //Create custom class loader
        MyClassLoader classLoader = new MyClassLoader("d:/");
        try {
            Class<?> clazz = classLoader.loadClass("TestMain");
            System.out.println("I am by"+clazz.getClassLoader().getClass().getName()+"Class loader loaded.");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Print

I'm from com kmning. wallet. jvm. Myclassloader is loaded by the class loader

Destroy the parental delegation model

The parental delegation model is not a model with mandatory constraints, but a class loader implementation recommended by Java designers to developers. In the Java world, most class loaders follow this model, but there are exceptions. Until the emergence of Java modularization, the parental delegation model has been "broken" on a large scale for three times.

The first "destruction" of the parental delegation model actually occurred before the emergence of the parental delegation model - jdk1 2 "ancient" times before its appearance. Due to the parental delegation model in jdk1 2, but the concept of class loader and abstract class java Lang. classloader already existed in the first version of Java. Faced with the existing user-defined class loader code, Java designers had to make some compromises when introducing the parental delegation model. In order to be compatible with these existing codes, they can no longer use technical means to avoid the possibility that loadclass () is covered by subclasses. They can only be used in jdk1 Java after 2 Lang. classloader adds a new protected method findClass() and guides the user to rewrite this method as much as possible when writing class loading logic instead of writing code in loadClass(). In the previous section, we have analyzed the loadclass () method, and the specific logic of parent delegation is implemented here. According to the logic of the loadclass () method, if the parent class fails to load, it will automatically call its own findclass () method to complete the loading, which will not affect the user to load the class according to their own wishes, It can also ensure that the newly written class loader complies with the parental delegation rules.

The second "destruction" of the parent delegation model is caused by the defects of the model itself. The parent delegation solves the consistency problem of the basic types when various class loaders cooperate (the more basic classes are loaded by the upper loader). The basic types are called "foundation" because they are always inherited by user code The called API exists, but there are often no absolutely unchanged perfect rules in program design. What should we do if there are basic types and we have to call back the user's code?

This is not impossible. A typical example is JNDI service. JNDI is now a standard service of Java. Its code is loaded by the startup class loader (added to rt.jar in JDK1.3). It must belong to a very basic type in Java. However, the purpose of JNDI is to find and centrally manage resources. It needs to call the code of JNDI service provider interface (SPI) implemented by other manufacturers and deployed under the ClassPath of the application. Now the problem comes. It is impossible to recognize and load these codes by starting the class loader. What should we do?

In order to solve this dilemma, the Java design team had to introduce a less elegant design: Thread Context ClassLoader. This class loader can be implemented through Java Set the setcontext classloader () method of lang. thread class. If it is not set when creating a thread, it will inherit one from the parent thread. If it is not set in the global scope of the application, this class loader is the application class loader by default.

With the thread context class loader, the program can do some "fraud" things. JNDI service uses this thread context class loader to load the required SPI service code. This is a behavior that the parent class loader requests the child class loader to complete class loading. This behavior actually opens up the hierarchy of the parent delegation model to use the class loader in reverse. It has violated the general principles of the parent delegation model, but it is also helpless. SPI loading in Java is basically completed in this way, such as JNDI, JDBC, JCE, JAXB and JBI. However, when there are more than one SPI service provider, the code can only be hard coded according to the type of specific provider. In order to eliminate this extremely inelegant implementation method, in JDK6, JDK provides Java util. Serviceloader class, with the configuration information in META-INF/services and the responsibility chain mode, provides a relatively reasonable solution for SPI loading.

The third "destruction" of the parental delegation model is caused by the user's pursuit of program dynamics. The "dynamics" here refers to some very "hot" nouns: Code Hot Swap, module Hot Deployment, etc. To put it bluntly, we hope that Java applications can be connected to the mouse and U SB flash disk like our computer peripherals, and can be used immediately without restarting the machine. If the mouse has a problem or needs to be upgraded, change the mouse without shutting down or restarting. For personal computers, restarting once is actually no big deal, but for some production systems, shutting down and restarting once may be listed as a production accident. In this case, Hot Deployment is very attractive to software developers, especially large-scale system or enterprise software developers.

Keywords: Java jvm Back-end

Added by clicket on Sun, 30 Jan 2022 22:13:53 +0200