preface

After JDK9, the concept of modularity is used, and there are some changes compared to JDK8 and previous classloaders.

Class loader

Loader class changes

In terms of class loaders, the platform class PlatformClassLoader replaces the extension class loader, and the boot class loader and system class loader remain unchanged.

The bootstrap class loader is implemented in a virtual machine by class libraries and code. For backward compatibility, it still uses the null representation in the program.

// jdk11 test
// Example 01
public class ClassLoaderDemo{
    public static void main(String[] args){
        String str = new String("hello bootstrap classloader");
        System.out.println(str.getClass().getClassLoader()); // null

        Date date = new Date(System.currentTimeMillis()); 
        // jdk.internal.loader.ClassLoaders$PlatformClassLoader@6cd8737
        System.out.println(date.getClass().getClassLoader()); 

        // // jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dcSystem.out.println(ClassLoaderDemo.class.getClassLoader()); }}Copy the code

As you can see from the output, the String class is loaded by ApplicationClassLoader, the Date class is loaded by PlatformClassLoader, and our custom class is still loaded by AppClassLoader.

The loading path changes

Modularity breaks the source code into smaller modules, each of which has a class loader responsible for loading its own classes.

  • byBootClassLoaderLoaded modules
java.base            java.security.sasl
java.datatransfer    java.xml
java.desktop         jdk.httpserver
java.instrument      jdk.internal.vm.ci
java.logging         jdk.management
java.management      jdk.management.agent
java.management.rmi  jdk.naming.rmi
java.naming          jdk.net
java.prefs           jdk.sctp
java.rmi             jdk.unsupported
Copy the code
  • byPlatformClassLoaderLoaded modules
java.activation    java.xml.ws.annotation  jdk.desktop         java.compiler
javafx.base        jdk.dynalink            java.corba          javafx.controls
jdk.javaws         java.jnlp               javafx.deploy       jdk.jsobject
java.scripting     javafx.fxml             jdk.localedata      java.se
javafx.graphics    jdk.naming.dns          java.se.ee          javafx.media
jdk.plugin         java.security.jgss      javafx.swing        jdk.plugin.dom
java.smartcardio   javafx.web              jdk.plugin.server   java.sql
jdk.accessibility  jdk.scripting.nashorn   java.sql.rowset     jdk.charsets
jdk.security.auth  java.transaction        jdk.crypto.cryptoki jdk.security.jgss 
java.xml.bind      jdk.crypto.ec           jdk.xml.dom         java.xml.crypto
jdk.crypto.mscapi  jdk.zipfs               java.xml.ws         jdk.deploy
Copy the code
  • byAppClassLoaderLoaded modules
jdk.aot               jdk.jdeps
jdk.attach            jdk.jdi
jdk.compiler          jdk.jdwp.agent
jdk.editpad           jdk.jlink
jdk.hotspot.agent     jdk.jshell
jdk.internal.ed       jdk.jstatd
jdk.internal.jvmstat  jdk.pack
jdk.internal.le       jdk.policytool
jdk.internal.opt      jdk.rmic
jdk.jartool           jdk.scripting.nashorn.shell
jdk.javadoc           jdk.xml.bind*
jdk.jcmd              jdk.xml.ws*
jdk.jconsole
Copy the code

Note that in the Example 01 from class Loading > Changes to the loader category above, the Date class is a Date from java.sql, so it is loaded by PlatformClassLoader.

Changes in inheritance structure

Before JDK9, both the extension class loader and the system class loader inherit from URLClassLoader. As mentioned earlier, if you want to customize the class loader more easily, you can inherit directly from URLClassLoader.

After JDK9, URLClassLoader is removed, the bootloader has a concrete implementation of BootClassLoader, and all three classes inherit from BulitinClassLoader.

Changes in using class loaders

We can use the class loader to load a class and get an instance of it. Previously we could use the newInstance() method to get an object instance.

Starting from JDK9, newInstance method of ClassLoader has been marked as obsolete, and it is recommended to use the following method instead:

clazz.getDeclaredConstructor().newInstance()
Copy the code

The original method must ensure that the class has a total empty constructor, while the new method, in the form of reflection, can be applied to any situation.

public class ClassLoaderDemo03 {

    // Only common parameter constructors, no empty constructors
    public ClassLoaderDemo03(String info){
        System.out.println(info);
    }

    public static void main(String[] args) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        // <= jdk8
        try {
            / / exception
            Object obj01 = (ClassLoaderDemo03) loader.loadClass("com.zcat.jvm.classloader.ClassLoaderDemo03").newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // >= jdk9
        try {
            // The instance was successfully created
            Object obj01 = (ClassLoaderDemo03) loader.loadClass("com.zcat.jvm.classloader.ClassLoaderDemo03")
                    .getDeclaredConstructor(String.class)
                    .newInstance("Object constructed with >=jdk9");
        } catch(Exception e) { e.printStackTrace(); }}}Copy the code

The output is as follows:

Java. Lang. InstantiationException: com. Zcat. The JVM. This. ClassLoaderDemo03 use > = jdk9 constructed objectCopy the code

Changes to parental delegation mode

The basic structure of the parent delegate pattern remains, but the delegate relationship between class loaders changes slightly.

Each class belongs to a module, so when receiving the request to load a class, if the module to which the class belongs can be found, it will first delegate to the class loader of the module to load; If no such module is found, it is first delegated to the parent class loader.

We can see the loading logic in the BuiltinClassLoader loadClassOrNull() method:

protectedClass<? > loadClassOrNull(String cn,boolean resolve) {
    synchronized (getClassLoadingLock(cn)) {
        // Whether it has already been loadedClass<? > c = findLoadedClass(cn);if (c == null) {
            // Not loaded, first find the corresponding module
            LoadedModule loadedModule = findLoadedModule(cn);
            if(loadedModule ! =null) {
                // Find the corresponding module and delegate it to the class loader responsible for that module
                BuiltinClassLoader loader = loadedModule.loader();
                if (loader == this) {
                    if(VM.isModuleSystemInited()) { c = findClassInModuleOrNull(loadedModule, cn); }}else{ c = loader.loadClassOrNull(cn); }}else {
                // No module found, delegate to parent class loader
                if(parent ! =null) {
                    c = parent.loadClassOrNull(cn);
                }
                if (c == null&& hasClassPath() && VM.isModuleSystemInited()) c = findClassOnClassPathOrNull(cn); }}}if(resolve && c ! =null) resolveClass(c);
        returnc; }}Copy the code

This change also breaks the parental delegation, so if the class to be loaded is in the module that the BootClassLoader is responsible for, the BuiltinClassLoader will be skipped and delegated directly to the BootClassLoader.