I’ve covered only a little bit of class loaders in my in-depth understanding of the JVM class loading mechanism, but this article takes a closer look at how class loaders work in Java.

Class loader

The first stage of class loading requires the fully qualified name of a class to get the binary byte stream describing that class. The module that does this is the class loader.

Class loaders do more than just load classes, but they do much more in Java programs. A class in Java is unique not only in terms of the class itself, but also in terms of its loader. In layman’s terms, comparing two classes for equality only makes sense if they are loaded by the same Class loader. Otherwise, even if two classes are from the same Class file and loaded by the same VIRTUAL machine, they are not equal as long as the Class loader is different.

Here’s a code from Zhou Zhiming’s book:

import java.io.IOException;
import java.io.InputStream;

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);
                    returndefineClass(name,b,0,b.length); } catch (IOException e) { throw new ClassNotFoundException(name); }}}; Object obj = myLoader.loadClass("ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());  //class ClassLoaderTest
        System.out.println(obj instanceof  ClassLoaderTest);  //false}}Copy the code

After the comment, we print the output. We use our own class loader to load the generated class file, generate the class class, and instantiate the obj object. Obj is the object instantiated by ClassLoaderTest, but the second sentence returns false because there are two ClassLoaderTest classes in the system. One is loaded by the system application class loader, and the other is loaded by our custom class loader. It’s from the same class file, but it’s two classes.

We talked about application loaders, custom class loaders and so on, but don’t worry, keep reading.

Parental delegation model

What is the parental delegation model? As we mentioned earlier, there are only two types of loaders in the JVM:

  • Bootstrap ClassLoader: C++ implementation, part of the virtual machine
  • User-defined Class Loader: Java language implementation, independent of VMS, and inherited fromjava.lang.ClassLoader

In general, when we talk about classloaders, we break it down into more detail, as shown in the following figure:

Class loader

The following discussion will be based on this diagram.

Most Java programs use one of three system-provided classloaders.

  • Bootstrap ClassLoader: this class will take care of the<JAVA_HOME>\lib\Directory, or-XbootclasspathA class library in the path specified by the specified directory and recognized by the VM is loaded into the VM memory, such as rt.jar. The library is recognized only by the file name. If the name does not match, it will not be loaded even in this directory. The boot class loader cannot be directly referenced by Java programs. If you need to delegate the loading request to the boot class loader when writing a custom class loader, use NULL instead.
  • Extension ClassLoaderThe class loader is created bysun.misc.Launcher $ExtClassLoaderImplementation, which is responsible for loading<JAVA_HOME>\lib\extDirectory, or byjava.ext.dirsAll class libraries in the path specified by the system variable.
  • Application ClassLoader: This class loader is created bysun.misc.Launcher $App-ClassLoaderThe implementation. The loader is created by ClassLoadergetSystemClassLoader()Method, so it is commonly called the system classloader. Generally, it loads the libraries specified by the user’s ClassPath. Developers use this class loader directly, and if they do not define their own class loader, the application load class is the default class loader in the program.

So let’s explain what is the parental delegation model?

The parent delegate model requires that all class loaders have their own parent class loaders except the top-level Bootstrap ClassLoader. The parent-child relationship is generally not implemented by inheritance, but by composition. Its basic workflow is as follows: If a classloader receives a class-loading request, it doesn’t first try to load the class itself. Instead, it delegates the request to the parent classloader, which is true at every level, so that eventually all requests are passed to the top level starting classloader. Only if the parent loader returns that it was unable to complete the load request (that is, it did not find the required class in its search scope) will the child loader attempt to load it itself.

What are the benefits of using the parent delegate model?

The most immediate benefit of using the parent delegate model is that Java classes have a hierarchy of priorities along with their classloaders, such as jaba.lang.Object, which is stored in rt.jar and which class loaders load. The final delegate is to the uppermost boostrap ClassLoader, so the Object class is the same class in each ClassLoader environment of the program. If the parent delegate model is not used and each class loads Object separately, there will be various versions of Object classes in the system, resulting in chaos.

Let’s look at the parent delegate mode from the ClassLoader source:

protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, checkifthe class has already been loaded Class<? > c = findLoadedClass(name); // Check if the class is loadedif(c == null) {long t0 = system.nanotime (); try {if(parent ! C = parent. LoadClass (name, parent) {// if parent is not null, call parent loadClass to loadClass c = parent.false);   
                    } elseC = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrownif class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass inorder // 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);
            }
            returnc; }}Copy the code

From the above code, there are three steps to loading a class with the specified name:

  1. callfindLoadedClass(String)To check if the class has been loaded;
  2. Calling the parent classloadClassMethod, if the parent class is empty, the virtual machine’s built-in boot class loader is called to load;
  3. callfindClass(String)To find the class.

Because loadClass encapsulates the parental delegation model, the Java standard overrides the findClass() method when developing your own classloaders.

Typically, a Java virtual machine loads a class from the file system, but some classes don’t necessarily come from a single file. They can also come from other sources, such as the network, encrypted files, etc. Suppose we write our own class loader to load the class files downloaded by the server.

Example use code:

ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main".true).newInstance();Copy the code

When defining, we override the findClass method:

class NetworkClassLoader extends ClassLoader {
         String host;
         int port;

         public Class findClass(String name) {
             byte[] b = loadClassData(name);
             return defineClass(name, b, 0, b.length);
         }

         private byte[] loadClassData(String name) {
             // load the class data from the connection
              . . .
         }
     }Copy the code

Also important here is the defineClass function, which converts a set of binary bytes into an instance of a Class, which is then parsed by subsequent classloading processes. The next steps are back to understanding the JVM class loading mechanism in depth.