Java class loading mechanism (class loader, parent delegation model, hot deployment example)

Java class loading mechanism

  • class loader
    • Class loader execution flow
    • Types of class loaders
    • Relationships between loaders
    • Main method of ClassLoader
    • The difference between Class.forName() and ClassLoader.loadClass()
  • Parental delegation model
    • Parental delegation class loading process
    • Advantages and Disadvantages
  • Simple example of hot deployment

Class loader

Execution process of class loader

Types of class loaders

AppClassLoader
Application class loader, the default system class loader, is responsible for loading classes in the Java application classpath

Set classpath java -cp D:\aaa Main.class Get classpath
System.getProperty(“java.class.path”)

ExtClasLoader
The extension class loader is responsible for loading java classes in the extension directory.

Set the extension directory: java -Djava.ext.dirs=”D:\aa” Main.class
Get the extension directory: System.getProperty(“java.ext.dirs”)

Starting from java9, the extension mechanism is removed and the loader is replaced by PlatFormClassLoader

BootStrapClassLoader
Start the class loader, responsible for loading the JDK core class library (/jre/lib)
Written in C++/C language, cannot be printed through java code

User-defined ClassLoader
Custom class loader that inherits abstract class ClassLoader

Relationship between loaders


From a JVM perspective, there are two class loaders, BootStrapClassLoader and java custom class loader
Among them, BootStrapClassLoader is the highest level class loader written in C/C++ language.

Java’s custom class loaders all inherit from the abstract class ClassLoader, and have an internal parent attribute pointing to the parent loader (it is composition, not inheritance)
The parent attribute of ExtClassLoader is null, which is actually associated with BootStrapClassLoader through the native method, so the parent class loader of ExtClassLoader is BootStrapClassLoader

The class loaders that come with JDK are: BootStrapClassLoader, ExtClassLoader, AppClassLoader
The three are not an integrated relationship, but a combination

Users can inherit ClassLoader to implement their own class loader. The default parent class loader is AppClassLoader

Main methods of ClassLoader

//Return the super class loader of this class loader
public final ClassLoader getParent()
 
//Load the class named name and return the result as an instance of the java.lang.Class class. If the class is not found,
//Return ClassNotFoundException exception. The logic in this method is the implementation of the parent delegation model
public Class<?> loadClass(String name) throws ClassNotFoundException
 
//Find the class with the binary name name, and the returned result is an instance of the java.lang.Class class.
//This is a protected method. The JVM encourages us to override this method. The custom loader needs to follow the parent delegation mechanism.
//This method will be called by the loadClass() method after checking the parent class loader.
protected Class<?> findClass(String name) throws ClassNotFoundException
 
//Convert to an instance of Class based on the given byte array b. The off and len parameters represent the position and length of the actual Class information in the byte array.
//The byte array b is obtained from the outside by ClassLoader.
//This is a protected method and can only be used in custom ClassLoader subclasses.
protected final Class<?> defineClass(String name, byte[] b,int off,int len)
 
//Link a Java class specified. Using this method, the Class object of the class can be created and parsed at the same time.
//The link stage is mainly to verify the bytecode.
//Allocate memory for class variables and set initial values while converting symbol references in bytecode files into direct references.
protected final void resolveClass(Class<?> c)
 
//Find the loaded class named name, and the returned result is an instance of the java.lang.Class class. This method is final and cannot be modified.
protected final Class<?> findLoadedClass(String name)
 
//It is also an instance of ClassLoader. The ClassLoader represented by this field is also called the parent of this ClassLoader.
//During the class loading process, ClassLoader may hand over certain requests to its own parents for processing.
private final ClassLoader parent;

The difference between Class.forName() and ClassLoader.loadClass()

public static Class forName(String className)

  1. is a class method
  2. Use system class loader for loading by default
  3. Load a specified class, initialize the class, execute static code blocks in the class, and assign values to static variables. It is generally used to load drivers, such as jdbc drivers.

public Class loadClass(String name)

  1. is a member method
  2. Lazy loading, just loading, will not parse or will not initialize the reflected class

Parental delegation model

rents Delegation Model
Note: Parents do not mean that there are two class loaders of the parent. There is actually only one parent, the parent loader, and it is an attribute of the loader, not inheritance. It can be understood as hermaphrodite.

Parental delegation class loading process

  1. When a class starts to be loaded, it will first determine whether the class has been loaded. If it has been loaded, the loaded Class object will be returned.
  2. If it has not been loaded, pass the loading request to the upper class loader through the parent attribute for loading.
  3. Call all the way to the extended class loader (ExtClassLoader). If no loaded Class object is found, parent == null at this time, pass it to BootStrapClassLoader for class loading through the findBootstrapClassOrNull() method.
  4. If BootStrapClassLoader fails to load successfully, its lower class loader begins to try to load
  5. If the bottom class loader (user-defined class loader) is not loaded successfully, a ClassNotFoundException exception is thrown.

In one sentence: The child class loader calls the parent class loader to load the class

The source code is as follows:

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) {<!-- -->
                    // If there is a parent, hand it to the parent class loader for loading.
                    c = parent.loadClass(name, false);
                } else {<!-- -->
                    //No parent, delegate to BootStrapClassLoader for loading through native method
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {<!-- -->
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {<!-- -->
                //If the parent class does not load the Class object, start trying to load it yourself
                long t1 = System.nanoTime();
                //According to the fully qualified name of the class, obtain the Class object, which is divided into two steps
                //1. Get the bytecode object based on the fully qualified name of the class
                //2. Based on the binary stream of bytecode, call the defineClass() method to generate a Class object
                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;
    }
}

Advantages and Disadvantages

advantage:

  1. Prevent repeated loading and ensure the global uniqueness of a class

When a class loader wants to load a class, it is first handed over to the parent class loader for loading, and the loading process is controlled by a syncronized lock. Avoid loading the same class repeatedly

  1. Ensure the security of the JVM and prevent users from replacing classes in the core class library with classes written by themselves

For example: if the user defines a java.lang.String class, through the parent delegation model, the String class in the core class library will be loaded and will not be replaced by the user-defined String class.

shortcoming:
The parent class loader cannot access the classes loaded by the child class loader. Sometimes it is necessary to break the parental delegation model, such as Tomcat

Simple example of hot deployment

public class MyClassLoader extends ClassLoader{<!-- -->
    /**
     * file path
     */
    final private String path;

    protected MyClassLoader(String path) {<!-- -->
        this.path = path;
    }

    /**
     * Rewrite findClass and implement your own class loader
     * 1. Get the byte stream of the class file
     * 2. Call defineClass to convert the byte stream into a CLass object
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {<!-- -->
        try {<!-- -->
            // Read the binary stream of bytecode
            byte[] b = loadClassFromFile(name);
            //Call the defineClass() method to create a Class object
            Class<?> c = defineClass(name, b, 0, b.length);
            return c;
        } catch (IOException e) {<!-- -->
           throw new ClassNotFoundException(name);
        }
    }

    /**
     * If you want to follow the parent delegation model, there is no need to override the loadClass method
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {<!-- -->
        //For the convenience of testing, here we simply break the parent delegation model and load it from the custom class loader first.
        //If you don't want to break the parent delegation model, the path path can be set to a path other than classpath, and manually copy the class file to the corresponding directory.
        synchronized (getClassLoadingLock(name)) {<!-- -->
            Class<?> c = findLoadedClass(name);
            if (c == null) {<!-- -->
                //Load from the custom class loader first, breaking the parent delegation model here
                try {<!-- -->
                    c = findClass(name);
                } catch (ClassNotFoundException ignored) {<!-- -->
                }
                //The custom class loader fails to load and is handed over to the parent class loader.
                if(c == null){<!-- -->
                    return super.loadClass(name);
                }
            }
            return c;
        }
    }

    private byte[] loadClassFromFile(String name) throws IOException {<!-- -->
        String fileName = name.replace('.', File.separatorChar) + ".class";
        String filePath = this.path + File.separatorChar + fileName;

        try (InputStream inputStream = new FileInputStream(filePath);
             ByteArrayOutputStream byteStream = new ByteArrayOutputStream()
        ) {<!-- -->
            int nextValue;
            while ((nextValue = inputStream.read()) != -1) {<!-- -->
                byteStream.write(nextValue);
            }
            return byteStream.toByteArray();
        }
    }
}
public class ApplicationConfig {<!-- -->

    public void getConfig(){<!-- -->
        System.out.println("This is my configuration...");
    }

}
public class HotDeploymentTest {<!-- -->
    public static void main(String[] args) throws Exception{<!-- -->
        Scanner sc = new Scanner(System.in);
        while(true){<!-- -->
            MyClassLoader loader = new MyClassLoader("E:\Work\classloader\target\classes");
            Class<?> aClass = loader.loadClass("cn.rwto.sample.ApplicationConfig");
            Object applicationConfig = aClass.newInstance();

            Method method = aClass.getMethod("getConfig");
            method.invoke(applicationConfig);
            
            Thread.sleep(5000);
        }
    }
}



Without closing the process, modify the code and recompile. The console finds that the printed content changes accordingly, and hot deployment is completed!