java class loading subsystem (class loader) and parental delegation mechanism

Keywords: Java jvm

Java class loading steps

1. Loading

The main task of the loading phase is to convert the classes in the. Class file into java.lang.Class objects. The specific steps are as follows:

  1. Get the binary byte stream defining a class through the fully qualified name of the class
  2. The static storage structure represented by this byte stream is transformed into the runtime data structure of the method area
  3. Create a java.lang.Class object of this class in the heap as an access to various data of this class in the method area

2. Connect

2.1 verification

The main purpose of validation is to ensure the correctness of the loaded classes. include:

  1. Verification of file format
  2. Validation of metadata
  3. Bytecode verification
  4. Verification of symbol references

Although the validation phase is important, you can also skip validation through Xverify:none.

2.2 preparation

The preparation phase is mainly to allocate memory for class variables and set the initial value of class variables.

2.3 analysis

The parsing phase is the process in which the virtual machine replaces the symbolic reference in the constant pool with a direct reference (pointer, offset).

3. Initialization

The initialization phase is mainly to initialize class variables. There are two ways to initialize:

  1. Specifies the initial value when declaring a class variable
    static int i = 1;
    
  2. Use static code blocks to specify initial values for class variables
    static String s;
    static {
        s = "";
    }
    

4. The difference between preparation and initialization:

public static int i=123;

In the preparation phase, i will be assigned as the default value of int type 0, and i will be assigned as 123 in the initialization phase

public static final int j=123;

However, for final modified variables, they will be directly initialized to 123 in the preparation stage. Because javac will generate the ConstantValue attribute for J during compilation, the virtual machine will directly assign a value to j according to the ConstantValue setting in the preparation phase:

JVM initialization steps:

  1. If the class has not been loaded and connected, load and connect the class first
  2. If the direct parent class of this class has not been initialized, initialize its direct parent class first (the first step will be returned when initializing the direct parent class, so the Object class is always initialized first)
  3. If there are initialization statements in the class, the system executes these initialization statements in turn

Class loader

The "load" step of the class loading subsystem is completed by some subclasses of the ClassLoader class. The JVM provides a three-tier ClassLoader:

  1. Bootstrap ClassLoader
  2. Extension ClassLoader
  3. Application ClassLoader
  4. In addition, the user-defined class loader Custom ClassLoader is allowed to inherit from the ClassLoader abstract class and override the findClass() method

We can use the following code to see the loading paths of each class loader:

System.out.println("Bootstrap ClassLoader Loaded Directory:");
String bootstrapLoadingPath = System.getProperty("sun.boot.class.path");
String[] bootstrapLoadingPaths = bootstrapLoadingPath.split(";");
for (String s : bootstrapLoadingPaths) {
    System.out.println(s);
}
System.out.println();
System.out.println("Extension ClassLoader Loaded Directory:");
String extensionLoadingPath = System.getProperty("java.ext.dirs");
String[] extensionLoadingPaths = extensionLoadingPath.split(";");
for (String s : extensionLoadingPaths) {
    System.out.println(s);
}
System.out.println();
System.out.println("Application ClassLoader Loaded Directory:");
String applicationLoadingPath = System.getProperty("java.class.path");
String[] applicationLoadingPaths = applicationLoadingPath.split(";");
for (String s : applicationLoadingPaths) {
    System.out.println(s);
}

result:

Bootstrap ClassLoader Loaded Directory:
D:\Environments\jdk-8u131\jre\lib\resources.jar
D:\Environments\jdk-8u131\jre\lib\rt.jar
D:\Environments\jdk-8u131\jre\lib\sunrsasign.jar
D:\Environments\jdk-8u131\jre\lib\jsse.jar
D:\Environments\jdk-8u131\jre\lib\jce.jar
D:\Environments\jdk-8u131\jre\lib\charsets.jar
D:\Environments\jdk-8u131\jre\lib\jfr.jar
D:\Environments\jdk-8u131\jre\classes

Extension ClassLoader Loaded Directory:
D:\Environments\jdk-8u131\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext

Application ClassLoader Loaded Directory:
D:\Environments\jdk-8u131\jre\lib\charsets.jar
D:\Environments\jdk-8u131\jre\lib\deploy.jar
D:\Environments\jdk-8u131\jre\lib\ext\access-bridge-32.jar
D:\Environments\jdk-8u131\jre\lib\ext\cldrdata.jar
D:\Environments\jdk-8u131\jre\lib\ext\dnsns.jar
D:\Environments\jdk-8u131\jre\lib\ext\jaccess.jar
D:\Environments\jdk-8u131\jre\lib\ext\jfxrt.jar
D:\Environments\jdk-8u131\jre\lib\ext\localedata.jar
D:\Environments\jdk-8u131\jre\lib\ext\nashorn.jar
D:\Environments\jdk-8u131\jre\lib\ext\sunec.jar
D:\Environments\jdk-8u131\jre\lib\ext\sunjce_provider.jar
D:\Environments\jdk-8u131\jre\lib\ext\sunmscapi.jar
D:\Environments\jdk-8u131\jre\lib\ext\sunpkcs11.jar
D:\Environments\jdk-8u131\jre\lib\ext\zipfs.jar
D:\Environments\jdk-8u131\jre\lib\javaws.jar
D:\Environments\jdk-8u131\jre\lib\jce.jar
D:\Environments\jdk-8u131\jre\lib\jfr.jar
D:\Environments\jdk-8u131\jre\lib\jfxswt.jar
D:\Environments\jdk-8u131\jre\lib\jsse.jar
D:\Environments\jdk-8u131\jre\lib\management-agent.jar
D:\Environments\jdk-8u131\jre\lib\plugin.jar
D:\Environments\jdk-8u131\jre\lib\resources.jar
D:\Environments\jdk-8u131\jre\lib\rt.jar
D:\Code\Code-Java\out\production\test
D:\ProgramSoftwares\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar

The inheritance relationship between class loaders is shown in the figure below:

Detailed process of class loading

For example, there is a com.philpy.HelloWorld class

  1. java.exe will call the underlying jvm.dll to create a JVM instance and Bootstrap ClassLoader

  2. Bootstrap ClassLoader will create an instance of sun.misc.Launcher launcher; While creating the launcher, Extension ClassLoader and Application ClassLoader are also created, because ExtClassLoader and AppClassLoader are both static inner classes of the launcher

  3. Use the getClassLoader() method of the launcher to obtain your own class loader. The instance is AppClassLoader instance by default (the parent loader of AppClassLoader is ExtClasssLoader):

  4. classLoader calls the loadClass() method to load the class HelloWorld to run:

    loader.loadClass("com.philpy.HelloWorld");
    
  5. When loading is complete, the JVM executes the main() method entry of the HelloWorld class

  6. Program execution completed

  7. JVM destruction

Parental delegation mechanism


By default, the Class loader instance is the AppClassLoader instance. Therefore, the loader will first find the Class template object of the Class by calling its own rewritten loadClass() method and fully qualified Class name. Before finding, it will first call the findLoadedClass() method to determine whether the Class has been loaded. If the return value of the method is not empty, it indicates that the Class has been loaded and returned directly; Otherwise, call the loadClass() method of the parent ClassLoader to find:

In the loadClass() method of the abstract parent class ClassLoader, the findLoadedClass() method is still called first to determine whether the class has been loaded. If the return value of the method is not empty, it indicates that the class has been loaded and returned directly; Otherwise, call the loadclass () method of your parent class loader to try loading. If the return value is not empty, it means that the class has been loaded and returned directly; Otherwise, continue to recursively call the loadclass () method of your parent class loader. If the parent class loader is empty, it means that the upward delegation has reached the end and reached the Bootstrap ClassLoader. The findbootstrap classornull() method will be called (the local method findbootstrap class() is called internally) to continue to judge whether the class has been loaded by the Bootstrap ClassLoader. At this time, the upward delegation has ended:

If it still returns null, the findclass () method is called to look down. ClassLoader's findClass() does nothing at all. It is specifically used to be implemented by subclasses (the custom class loader needs to inherit ClassLoader and override the findClass() method):

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

Therefore, the default fincClass() method is the override method of URLClassLoader. The findClass() method mainly does one thing: convert the package name in com.philpy.HelloWorld format into a class file in com/philpy/HelloWorld.class format. If the file can be loaded, try to convert it into a class object and return it through the define local method of secureclassloader. If ClassNotFoundException occurs or null is returned during the process, Go back to loadClass() and continue to look down until the class is found or no ClassNotFoundException is thrown:

Namely:

Custom class loader

The core of the custom class loader is to inherit the ClassLoader class and override the findClass() method. We can follow the example provided by the official Java:

package com.philpy;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyClassLoader extends ClassLoader {
    private final String baseDir;

    public MyClassLoader(String baseDir) {
        this.baseDir = baseDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = name.replace('.', '/').concat(".class");
        try {
            byte[] b = loadClassData(baseDir + path);
            return defineClass(name, b, 0, b.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    private byte[] loadClassData(String name) throws IOException {
        FileInputStream is = new FileInputStream(name);
        int len = is.available();
        byte[] bytes = new byte[len];
        is.read(bytes);
        is.close();
        return bytes;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        MyClassLoader loader = new MyClassLoader("D:\\test\\");
        Class<?> clazz = loader.loadClass("com.philpy.HelloWorld");
        System.out.println(clazz.getClassLoader());
        Object o = clazz.getConstructor().newInstance();
        Method method = clazz.getDeclaredMethod("sayHello");
        method.invoke(o);
    }
}

Test:



However, when there is also a HelloWorld class in my MyClassLoader path:


Run the code again:

It was found that the class loader became AppClassLoader. This is because there is a parental delegation mechanism. When looking down, if a class with the same package name and class name is found under the path of AppClassLoader, it will stop looking down and return directly. So we can't get the results we want. So how to destroy the parental delegation mechanism?

Destroy the parental delegation mechanism

In fact, it is also very simple to destroy the parental delegating mechanism. The parent delegate mechanism is mainly due to the parent.loadClass() method which is called in loadClass() method, which causes it to be delegated upward. Therefore, it is necessary to rewrite the loadClass() method if we want to destroy the parent delegation mechanism.

package com.philpy;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyClassLoader extends ClassLoader {
    private final String baseDir;

    public MyClassLoader(String baseDir) {
        this.baseDir = baseDir;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
        	// I just want the classes under "com.philpy" not to follow parental delegation
            if (!name.startsWith("com.philpy")) {
                c = this.getParent().loadClass(name);
            }else{
                c = findClass(name);
            }
        }
        return c;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = name.replace('.', '/').concat(".class");
        try {
            byte[] b = loadClassData(baseDir + path);
            return defineClass(name, b, 0, b.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    private byte[] loadClassData(String name) throws IOException {
        FileInputStream is = new FileInputStream(name);
        int len = is.available();
        byte[] bytes = new byte[len];
        is.read(bytes);
        is.close();
        return bytes;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        MyClassLoader loader = new MyClassLoader("D:\\test\\");
        Class<?> clazz = loader.loadClass("com.philpy.HelloWorld");
        System.out.println(clazz.getClassLoader());
        Object o = clazz.getConstructor().newInstance();
        Method method = clazz.getDeclaredMethod("sayHello");
        method.invoke(o);
    }
}

Modular class loader (after JDK9)

The class loaders mentioned above are all JDK1.8 class loaders. When encountering the parental delegation protection mechanism, the error messages are as follows:
However, if the Java modular system is introduced after JDK9, the class loader also has some changes:

The modular loader mainly has these changes:

  • Extension Class Loader is replaced by Platform Class Loader, which is a logical change. Since the whole JDK is built based on modularization (the original rt.jar and tools.jar are split into dozens of JMOD files), the Java class library naturally meets the extensibility requirements, Naturally, there is no need to keep < Java_ Home > \ lib \ ext directory
  • Platform class loader and application class loader are no longer derived from java.net.URLClassLoader. If there is a program directly
    If you rely on this inheritance relationship or the specific method of the URLClassLoader class, the code is likely to crash in JDK 9 and later. Now the startup class loader, platform class loader and application class loader are all inherited from jdk.internal.loader.BuiltinClassLoader. The logic of how to load classes from modules under the new modular architecture and the processing of resource accessibility in modules are implemented in BuiltinClassLoader
  • The startup class loader is now a class loader implemented in collaboration with the Java class library within the Java virtual machine. Although there is a Java class such as BootClassLoader, in order to maintain compatibility with the previous code, null will still be returned instead of an instance of BootClassLoader in all scenarios where the BootClassLoader is obtained (such as Object.class.getClassLoader())



There are also some changes in the parent delegation model after JDK9: when the platform and application class loader receives a class loading request, before delegating (findLoadedClass cannot be found) to the parent loader for loading, it is necessary to judge whether the class can belong to a system module (findLoadedModule). If such a attribution relationship can be found, The loader responsible for that module should be delegated to finish loading first:

Posted by JasperBosch on Sun, 12 Sep 2021 18:31:59 -0700