Java virtual machine classloader and parent delegation mechanism

Posted by darga333 on Tue, 29 Oct 2019 08:45:53 +0100

The so-called Class Loader is to load Java classes into the Java virtual machine.< Interviewer, stop asking me "Java virtual machine class loading mechanism" >The mechanism for loading class files is described in. This article focuses on the loader and parental delegation mechanism.

Class loader

There are three types of classloaders in the JVM: Bootstrap ClassLoader, ExtClassLoader and AppClassLoader. Different class loaders are responsible for loading classes in different regions.

Start class loader: this loader is not a Java class, but is implemented by the underlying c + +, which is responsible for storing the class library in the lib directory under JAVA_HOME, such as rt.jar. Therefore, the startup classloader does not belong to the Java class library and cannot be directly referenced by Java programs. When you write a custom classloader, if you need to delegate the load request to the bootstrap classloader, you can directly use null instead.

Extension class loader: implemented by sun.misc.Launcher$ExtClassLoader, it is responsible for loading all class libraries in the lib\ext directory under Java home or in the path specified by the java.ext.dirs system variable. Developers can directly use the extension class loader.

Application class loader: implemented by sun.misc.Launcher$AppClassLoader. Because this ClassLoader is the return value of getSystemClassLoader method in ClassLoader, it is also called system ClassLoader. It is responsible for loading the class library specified on the user's class path and can be used directly. If the class loader is not customized, it is the class loader by default.

You can print the loading path and related jar s in this way:

System.out.println("boot:" + System.getProperty("sun.boot.class.path"));
System.out.println("ext:" + System.getProperty("java.ext.dirs"));
System.out.println("app:" + System.getProperty("java.class.path"));

In the printed log, you can see the detailed path and which class libraries are included under the path. Due to the large amount of printed content, it will not be shown here.

Class loader initialization

In addition to the startup class loader, the extension class loader and application class loader are initialized by the sun.misc.Launcher class, while the Launcher class is loaded by the root class loader. The relevant codes are as follows:

public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        //Initialization extension class loader, constructor has no input parameter, unable to get start class loader
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        //Initialize application class loader, input parameter is extension class loader
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    // Set context class loader
    Thread.currentThread().setContextClassLoader(this.loader);
    
   //...
}

Parents Delegation Model

Parent delegation model: when a class loader receives a class loading request, it first requests its parent class loader to load, recursively in turn. When the parent class loader cannot find the class (according to the fully qualified name of the class), the child class loader will try to load.

The parent-child relationship in a parent delegation is usually not implemented by inheritance, but by using a combined relationship to reuse the code of the parent loader.

By writing test code and debug ging, we can find the combination relationship between different classloaders in the process of parent delegation.

It will be clearer to use a sequence diagram for this process.

Classloader × loadclass source code

ClassLoader class is an abstract class, but it does not contain any abstract methods. The custom class loader can be implemented by inheriting the ClassLoader class and overriding the findClass method. However, if the parent delegation model described above is broken to implement the custom class loader, you need to inherit the ClassLoader class and override the loadClass method and findClass method.

Some source codes of ClassLoader class are as follows:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
    //Lock the class first to avoid concurrent loading
    synchronized (getClassLoadingLock(name)) {
        //First, determine whether the specified class has been loaded.
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //If the current class is not loaded and the parent loader is not null, request the parent loader to load.
                    c = parent.loadClass(name, false);
                } else {
                   //If the current class is not loaded and the parent loader is null, request the root loader to load.
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }

            if (c == null) {
                long t1 = System.nanoTime();
               //If the parent class loader fails to load, it will be loaded by the current class loader.
                c = findClass(name);
                //Do some statistical operations
               // ...
            }
        }
        //Initialize this class
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

The above code also shows the hierarchy and composition relationship between different classloaders.

Why use the parental delegation model

The parental delegation model is to ensure the type security of Java core library. All Java applications need to reference at least the java.lang.Object class, which needs to be loaded into the Java virtual machine at runtime. If the loading process is completed by the custom class loader, there may be multiple versions of java.lang.Object classes, and these classes are incompatible.

Through the parent delegation model, the class loading of Java core library is completed by starting class loader, which ensures that all classes of Java core library of the same version are compatible with each other.

context class loader

Child class loaders all retain references to the parent class loader. But what if the class loaded by the parent class loader needs to access the class loaded by the child class loader? The most classic scenario is JDBC loading.

JDBC is a set of standard interface for accessing database developed by Java. It is included in java basic class library and loaded by root class loader. The implementation class libraries of each database manufacturer are introduced and used as a third-party dependency. This part of the implementation class libraries are loaded by the application class loader.

Get the code of Mysql connection:

//load driver
Class.forName("com.mysql.jdbc.Driver");
//Connect to database
Connection conn = DriverManager.getConnection(url, user, password);

DriverManager is loaded by the startup class loader. The database driver it uses (com.mysql.jdbc.Driver) is loaded by the application class loader. This is a typical class loaded by the parent class loader that needs to access the class loaded by the child class loader.

For the implementation of this process, see the source code of DriverManager class:

//Establishing the underlying method of database connection
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    //Get the caller's classloader
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        //Class loaded by the startup class loader, null, using context class loader
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

    //...

    for(DriverInfo aDriver : registeredDrivers) {
        //Using context class loader to load driver
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                //Load successfully, connect
                Connection con = aDriver.driver.connect(url, info);
                //...
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } 
        //...
    }
}

In the above code, pay attention to the rerouting Code:

callerCL = Thread.currentThread().getContextClassLoader();

This line of code gets ContextClassLoader from the current thread. Where is ContextClassLoader set? It is set in the Launcher source code above:

// Set context class loader
Thread.currentThread().setContextClassLoader(this.loader);

In this way, the so-called context class loader is essentially the application class loader. Therefore, the context class loader is just a concept proposed to solve the reverse access of classes. It is not a new class loader, but an application class loader in essence.

Custom class loader

The user-defined class loader only needs to inherit the java.lang.ClassLoader class, and then rewrite the findClass(String name) method, which indicates how to obtain the byte code stream of the class.

If you want to break the parent delegation specification, you need to override the loadClass method (the specific logical implementation of parent delegation). But this is not recommended.

public class ClassLoaderTest extends ClassLoader {

    private String classPath;

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

    /**
     * The logic of writing findClass method
     *
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // Get class file byte array of class
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            // Generate class object
            return defineClass(name, classData, 0, classData.length);
        }
    }

    /**
     * Write logic to get class file and convert it to byte stream
     *
     * @param className
     * @return
     */
    private byte[] getClassData(String className) {
        // Read bytes of class file
        String path = classNameToPath(className);
        try {
            InputStream is = new FileInputStream(path);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int num = 0;
            // Read bytecode of class file
            while ((num = is.read(buffer)) != -1) {
                stream.write(buffer, 0, num);
            }
            return stream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Full path to class file
     *
     * @param className
     * @return
     */
    private String classNameToPath(String className) {
        return classPath + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
    }

    public static void main(String[] args) {
        String classPath = "/Users/zzs/my/article/projects/java-stream/src/main/java/";
        ClassLoaderTest loader = new ClassLoaderTest(classPath);

        try {
            //Load the specified class file
            Class<?> object1 = loader.loadClass("com.secbro2.classload.SubClass");
            System.out.println(object1.newInstance().toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Print results:

SuperClass static init
SubClass static init
com.secbro2.classload.SubClass@5451c3a8

About SuperClass and SubClass in the previous article< Interviewer, stop asking me "Java virtual machine class loading mechanism" >The code has been pasted, so it will not be pasted here.

As can be seen from the above code, the user-defined class loader is implemented by overriding the path of findClass to obtain class.

So, what scenarios use custom classloaders? When the implementation of the classloader provided by JDK cannot meet our needs, we need to implement the classloader ourselves. For example, OSGi, code hot deployment and other fields.

Java 9 class loader modification

The above classloader model is prior to Java 8. In Java 9, the classloader has changed. Here is a brief introduction to the changes of relevant models. The details of the changes will not be expanded here.

Changes to directories in java9.

Class loader changes in Java 9.

In java9, application class loader can be delegated to platform class loader and startup class loader; platform class loader can be delegated to startup class loader and application class loader.

In java9, the startup class loader is implemented by the class library and code in the virtual machine. For backward compatibility, it is still represented by null in the program. For example, Object.class.getClassLoader() still returns null. However, not all Java se platforms and JDK modules are loaded by the boot loader.

For example, java.base, java.logging, java.prefs and java.desktop are the modules that start the classloader to load. Other Java se platforms and JDK modules are loaded by platform class loaders and application class loaders.

Java 9 no longer supports specifying the boot class path, - Xbootclasspath and - Xbootclasspath/p options and system attribute sun.boot.class.path. -The Xbootclasspath/a option is still supported and its value is stored in the system attribute of jdk.boot.class.path.append.

java9 no longer supports the extension mechanism. However, it keeps the extension ClassLoader under a new name called platform ClassLoader. The ClassLoader class contains a static method called getPlatformClassLoader(), which returns a reference to the platform class loader.

Summary

This article mainly introduces Java virtual machine classloader and parent delegation mechanism based on java8, and some changes in java8. Among them, the deeper changes in java9 can be further studied. This series continues to update, welcome to pay attention to WeChat public's "program new horizons".

Original link:< Java virtual machine classloader and parent delegation mechanism>

Interviewer series:


New vision of program: wonderful and growing

Topics: Java Database JDBC JDK