The introduction

As mentioned in the previous article “Introduction to the Java Virtual Machine”, when Java code is compiled, it generates the corresponding class bytecode files, which are first loaded into memory through the class loading subsystem when the program is started, and then sent to the execution engine for execution. In this article, the class loading mechanism and execution engine of the Java virtual machine will be thoroughly analyzed.

A brief introduction to class loading mechanism and loading process

The Java compiler generates a corresponding.class bytecode file for each.java file. The.class file records the virtual machine instructions after the Java code is converted. Each.class file starts with a specific identifier, magic number version, and so on.

When the JVM needs a class, the virtual machine loads it.classFile, for which the corresponding bytecode information is created after loadingClassObject, and this process is called class loading. But it’s important to note that the classloading mechanism is only responsibleclassThe execution engine determines whether a file can be loaded or not. Next, let’s look at the class loading process. As follows:



As shown in the figure above, the class loading process is divided into three steps and five phases: load, validate, prepare, parse, and initialize. The sequence of the four phases of load, validation, preparation, and initialization is determined. The parsing phase does not have to be, however, and in some cases can start after the initialization phase (also known as dynamic binding or late binding) in order to support the Runtime binding feature of the Java language.

1.1. Loading procedure

The load phase is the process of looking up Class file binary data by fully qualified name and loading it into memory. The general process will be divided into three steps:

  • ① Locate by fully qualified name lookup.classFile and get its binary byte stream data
  • ② Convert the static storage structure represented by the byte stream into a runtime data structure
  • ③ Create one for it in the middle of the heapClassObject as the entry point for the program to access this data

1.2. Connection procedure

The connection step consists of three stages: validation, preparation, and parsing. Of the three phases, the first two execution sequences are fixed, but the parsing phase is not necessarily so, and may occur after initialization.

1.2.1 Verification phase

The verification phase is used to ensure the correctness of the loaded Class and check whether the data in the Class byte stream meets VM requirements to ensure vm security. The verification stage mainly includes four kinds of verification: file format verification, metadata verification, bytecode verification and symbol reference verification.

  • ① File format verification: verify whether the byte stream conforms toClassSpecification of file formats
    • CA/FE/BA/BEThe magic number to verify
    • Verify the primary and secondary version numbers
    • Whether there are unsupported type validations for constant types in the constant pool
    • Whether an index in the pointer constant pool has specified constants that do not exist or do not conform to a type
  • ② Metadata verification: Semantic analysis of the information described by bytecode is carried out to ensure that the information described conforms to the requirements of Java language specifications
    • Whether a class has a parent class, exceptObjectIn addition, all classes should have parent classes
    • Whether the parent of a class inherits from a class that is not allowed to be inherited (byfinalModified class)
    • If the class is not abstract, whether it implements all the methods required in its parent class or interface
    • Whether a field/method of a class conflicts with a parent class. For example, methods with the same parameters return different values
  • ③ Bytecode verification: through data flow and control flow analysis, determine the program semantics is legal and logical
    • The method body of the class is verified and analyzed to ensure that it does not harm virtual machines during runtime
    • Ensure that at any time the data type of the operand stack and the sequence of instruction code work together, there is no such thing as putting one in the operand stackintType of data that is read according tolongType loading into the local variable table
    • Ensures that no jump instruction jumps to a bytecode instruction outside the method body
  • (4) Symbol reference verification: ensure that subsequent parsing actions can be correctly executed
    • Whether a class can be found for a fully qualified name described by a string
    • The accessibility of classes, fields, and methods in symbolic references is accessible to the current class
1.2.2 Preparation Stage

The preparation phase focuses on allocating memory space for static variables declared in the class and initializing them to default values (zero). It is important to note, however, that this default value does not refer to the value explicitly assigned in Java code, but to the default value for the data type. Static int I = 5; I’m just going to initialize I to 0.

Memory allocation here includes only class members (static members), whereas instance members are allocated together in heap space when concrete Java objects are created. Static members decorated with final are also not included, because final is assigned at compile time and initialization is displayed during preparation.

1.2.3. Analysis Phase

The parsing phase is the process of converting symbolic references in a class to direct references in the constant pool. It is worth noting that parsing operations are often performed by the JVM after initialization

Symbolic reference: A set of symbols that describe the target of a reference. The literal form of a symbolic reference is explicitly defined in the Class file format of the Java Virtual Machine Specification. A direct reference is a pointer to a target, a relative offset, or a handle that is indirectly located to the target

And the symbolic reference to direct reference process, It refers to seven types of symbols, including class or interface, field, class method, interface method, method type, method handle, and call point qualifier (CONSTANT_Class_info, CONSTANT_Fieldref_info, and CONSTANT_Methodref_in in the constant pool respectively) Xsl-fo, etc.).

1.3. Initialization Procedure

In the initialization step, we assign the correct initial value to the static variable of the class, that is, the initialization value specified when the static variable is declared and the assignment value in the static code block. This is essentially the process of executing the class constructor method

(). Initialization can be triggered in six ways, as follows:

  • (1) meetnew/getstatic/putstatic/invokestaticThese four bytecode instructions are triggered
    • new: Creates an instance object using the new keyword
    • getstatic: Reads a static field
    • putstatic: Sets a static field
    • invokestatic: Calls a static method
  • If the type has not been initialized, initialization will be triggered
  • ③ When initializing a class, if the parent class is not initialized, initialization of the parent class is triggered first
  • ④ When the VM starts, specify a main class to execute (the class containing the main method). The VM initializes the main class (such as the boot class of SpringBoot).
  • ⑤ When using the new dynamic language support in JDK7, if ajava.lang.invoke.MethodHandlerThe final parsing result of the instance isREF_getStatic REF_putStatic, REF_invokeStatic REF_newInvokeSpecialFour types of method handles, and the corresponding class of the method is not initialized, its initialization is triggered first
  • (6) If the implementation class of an interface is initialized, the interface must be initialized first

During the initialization phase, there are six and only six situations that trigger the initialization of a class, and these are called active references. Other than the above, using a class in any other way is considered a passive reference to the class and does not result in class initialization. Calling static fields of the parent class in a subclass, defining array references to the class, calling constants of the class, and so on, does not trigger class initialization.

At the same time, when a class is triggered for initialization, the general steps are as follows:

  • If the class has not been loaded and connected, the load and connect steps are performed first
  • If the current class has an immediate parent that is not initialized, the immediate parent is initialized first
  • Constructor methods execute instructions in the order in which the statements appear in the source text

If you are particularly interested in class initialization, you can also use examples to simulate various observations of active and passive references.

1.4. Use and uninstall

When a Class has fully gone through the Class loading process, Class objects have been generated in memory, and instance objects have been created for use in Java programs, this phase is called the use phase.

When a Class object is no longer referenced anywhere, that is, unreachable, the Class ends its life cycle and the data loaded by that Class is unloaded. But note:

Java virtual machine’s own Class loader loads of classes, in the virtual machine will not be unloaded throughout the life cycle of, because the JVM will always remain with the reference of these Class loaders, and the Class loader will always stay with their loading Class object references, so for the virtual machine, the Class object is always can be reached. However, classes loaded by user-defined class loaders can be unloaded.

Second, the JVM ClassLoader analysis

The classloader’s job is to read a Class’s binary byte stream data based on its fully qualified name, load it into memory, and convert it into a Class object corresponding to that Class. Virtual machines provide three types of loaders, which can also be implemented by themselves, as follows:

2.1 Bootstrap Bootstrap bootloader

Bootloader is also called bootloader or root classloader in some places, but it’s the same thing. The bootclass loader, implemented in C++, is part of the JVM itself and is responsible for loading into memory the core libraries in the

\lib path or jar packages in the path specified by the -xbootclasspath parameter.

Note: Since the JVM loads the class libraries with fully qualified names, if your file name is not recognized by the virtual machine, the bootstrap class loader will not load the JAR even if you drop it into the lib directory. For security reasons, Bootstrap starts the class loader to load only class files whose package names start with Java, Javax, and Sun.

The boot class loader provides loading services only for the JVM, and developers cannot directly use it to load their own classes.

Extension class loader

The classloader is implemented by Sun and is located in the Sun.misc.Launcher$ExtClassLoader in the HotSpot source directory. It is responsible for loading class libraries in the

\lib\ext directory or in the bitpath specified by the system variable -djava.ext. dir. It can be used directly by developers.

Application system class loader

Also known as the application classloader, also implemented by Sun, is located in the Sun.misc.Launcher$AppClassLoader in the HotSpot source directory. It is responsible for loading libraries under the system classpath java-classpath or -d java.class.path, which is the frequently used classpath path. Application class loaders can also be used directly by developers.

In general, the class loader is the default class loader program, we can use this. GetSystemClassLoader () method can directly access to it.

2.4. User custom class loader

In Java programs, the runtime is usually executed through the above three types of loaders. Of course, if you have special loading requirements, you can also customize the ClassLoader by inherits the ClassLoader class (more on this later).

2.5. Relationships among the four types of loaders

The class loader relationship chain analyzed above is as follows:

Bootstrap → Extension → Application system → User Custom class loader

The Bootstrap class loader is initialized at JVM startup and takes care of loading the ExtClassLoader and setting its parent to BootstrapClassLoader. After loading the ExtClassLoader, the BootstrapClassLoader will load the AppClassLoader system class loader and set its parent as the ExtClassLoader extension class loader. The class loaders you define are loaded by the system class loader, and AppClassLoader becomes their parent.

However, it is worth noting that there is no mutual inheritance or inclusion relationship between class loaders, only hierarchical reference relationship between parent loaders from top to bottom.

Let’s use Java code to briefly analyze the relationship between class loaders, as follows:

// Custom class loaders
public class ClassLoaderDemo extends ClassLoader {
    public static void main(String[] args){
        ClassLoaderDemo classLoader = new ClassLoaderDemo();

        System.out.println("Custom loader:" +
                classLoader);
        System.out.println("Custom loader's parent class loader:" +
                classLoader.getParent());
        System.out.println("Java program system default loader:" +
                ClassLoader.getSystemClassLoader());
        System.out.println("Parent loader of the system class loader:" +
                ClassLoader.getSystemClassLoader().getParent());
        System.out.println("Extend class loader's parent loader:"+ ClassLoader.getSystemClassLoader().getParent().getParent()); }}Copy the code

The following output is displayed:

Custom loader: com. SixstarServiceOrder. ClassLoaderDemo @ 6 d5380c2 customization of the parent class loader loader: Sun.misc.Launcher$AppClassLoader@18b4aac2 The default loader of the Java program: sun.misc.Launcher$AppClassLoader@18b4aac2 The parent loader of the system class loader: Sun.misc.Launcher$ExtClassLoader@45ff54e6 The parent of the extension class loader:null
Copy the code

Since BootstrapClassLoader is implemented by C++, the result is null when the parent class loader of ExtClassLoader is retrieved.

2.6 Class loader summary

The JVM’s classloading mechanism operates in on-demand mode, meaning that classes are not loaded at program startup, but are only loaded when a class is needed and found not loaded.

Class loaders in Java are organized into hierarchies with parent-child relationships. At the same time, the class loader also exists between the proxy pattern, when a class needs to be loaded, will first according to the hierarchical structure in turn check whether his father loader to load the class, if the parent layer has been loading can be used directly, on the contrary, if not be loaded from top to bottom in turn to ask, whether the scope can be loaded, whether to allow the current level of the loader loads, Load if possible.

Each Class loader has its own namespace, which is used to store the Fully Qualified Class Name of all classes loaded by itself. When the subclass loader searches for whether the parent Class loader has loaded a Class, It is matched in the namespace of the parent class by the permission name of the class. ClassLoaderId + PackageName + ClassName is used to determine whether two classes are the same. This means that two classes with identical package names and class names are allowed to exist during the running of Java programs. This is the isolation problem with Java classloaders, and the JVM addresses this problem by introducing parental delegation (more on that later).

As mentioned earlier, the subclass loader can check the classes loaded by the parent class loader, but this is irreversible, which means that the parent class loader cannot find the classes loaded by the subclass loader, which has visibility limitations. Classes loaded by the Bootstrap, Ext, and APP class loaders cannot be unloaded (as previously analyzed), but you can delete the current class loader and create a new one to load.

Outside: When Java loads classes, there are two types of explicit loading and implicit loading. Explicit loading means that the developer manually loads a class by calling the ClassLoader. For example, class.forname (name) or obj.getClass().getClassLoader().loadClass(). Implicit loading means that a class is not explicitly loaded in the program. It is passive loading. For example, when a class is loaded, the JVM automatically loads another class when the class references an object of another class.

Parent delegate mechanism in class loading subsystem

As mentioned earlier, the JVM introduced parental delegation (introduced in 1.2) to address class loader isolation. The core ideas of parental delegation are two things:

  • ① Check whether the class is loaded from the bottom up
  • ② Try to load classes from top to bottom

Let’s take a quick look at the loading process of the parent delegate mechanism.

3.1. Parent delegates class loading

  • (1) whenAppWhen it tries to load a class, it does not try to load the class directly. It first checks its namespace to see if the class has been loaded, and if not, delegates the class loading request to the parent class loaderExtcomplete
  • (2) whenExtWhen it tries to load a class, it does not try to load the class directly. It also queries its own namespace to see if the class has been loaded, and delegates the class loading request to the parent class loader firstBootstrapcomplete
  • (3) ifBootstrapLoad failure: the class that needs to be loaded is not thereBootstrapWithin the loading range, thenBootstrapThe classload request is redirected to the subclass loaderExtcomplete
  • (4) ifExtLoading failed, indicating that the class is not there eitherExtThe class loading request is finally redirected to the subclass loaderAppcomplete
  • 5. IfAppIf the loader fails to load, the class cannot be found by its fully qualified name and is thrownClassNotFoundExceptionabnormal

3.2. Advantages of parent delegation

When a JVM attempts to load a class, the lowest class loader receives the request and sends it to its upper class loader, as follows:



OK, so what are the advantages of adopting this model?

With parents to the advantage of the model is that: the Java class as the class loader has a priority hierarchies, do this has the advantage that can avoid a class reloading in different hierarchy of class loader, if the parent class loader have been loaded the class, so there is no need to subclass loader loaded again. For example, when a java.lang.String class is transferred over the network and needs to be loaded, the Bootstrap loader finds that the class has been loaded through parental delegation. Instead of loading the passed java.lang.String class, the Bootstrap loaded String.class is returned. In this way, Java’s core API classes can be effectively prevented from being tampered with at run time, thus ensuring that all subclasses share the same base class, reducing performance overhead and security risks.

3.3. The implementation principle of parental delegation in Java

We’ve looked briefly at Java’s class loading mechanism and the parent delegate mechanism, followed by a code-level look at the implementation of the parent delegate pattern in Java and some classloaders defined in Java.

In Java, all class loaders indirectly inherit from the ClassLoader class, including Ext and App (except Bootstrap, which is implemented in C++), as follows:

/ / sun. Misc. The Launcher class
public class Launcher {
    // sun.misc.Launcher class → constructor
    public Launcher(a){
        Launcher.ExtClassLoader var1;
        try {
            // The Ext class loader is initialized and the ExtClassLoader is created
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError(
                "Could not create extension class loader", var10);
        }
        try {
	        // Create the AppClassLoader and pass Ext to the App as the parent loader
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader");
        }
        
        // Set APP classloader to thread context classloader (more on that later)
        Thread.currentThread().setContextClassLoader(loader);
        // 省略......
    }
    
    // sun.misc.Launcher class → ExtClassLoader inner class
    static class ExtClassLoader extends URLClassLoader {
        // ExtClassLoader inner class → constructor
        public ExtClassLoader(File[] var1) throws IOException {
            // When Ext is initialized, the parent constructor is set to NULL
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this)
                         .initLookupCache(this); }}// Sun.misc.Launcher class → AppClassLoader inner class
    static class AppClassLoader extends URLClassLoader {}}/ / java.net.URLClassLoader class
public class URLClassLoader extends SecureClassLoader
        implements Closeable {}

/ / Java. Security. SecureClassLoader class
public class SecureClassLoader extends ClassLoader {}
Copy the code

The Ext class loader is an internal class of the sun.misc.Launcher class. The Ext class loader is created when the Launcher is initialized, and its parent class loader is forced to be null when the Ext class is initialized. After the Ext class loader is created, the App class loader is created, and the Ext class loader is set to be the parent of the App class loader when the AppClassLoader is created.

Ext and App class loaders inherit the URLClassLoader class, which is used to read various JAR packages, local classes, and class files passed over the network, by finding their bytecode, and then reading it into a byte stream. Finally, the Class object of the Class is created using the defineClass() method. The URLClassLoader class inherits the SecureClassLoader class, which is also an extension of the ClassLoader class. It adds several methods to verify the location of the source code and its certificate, as well as the permission definition class verification (mainly refers to the access to the class source code). We don’t normally deal with this class directly, but more with its subclass URLClassLoader.

The Ext and App class loaders inherit the ClassLoader class indirectly. The ClassLoader class is the top-level design class of Java class loading mechanism. It is an abstract class.

// ClassLoader → loadClass() method
protectedClass<? > loadClass(String name,boolean resolve)
    throws ClassNotFoundException
{
    / / lock
    synchronized (getClassLoadingLock(name)) {
        // First try to find the Class object from its own namespace by fully qualified nameClass<? > c = findLoadedClass(name);If ==null, class loading begins
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // Delegate the class loading task to its parent class loader
                if(parent ! =null) {
                    c = parent.loadClass(name, false);
                } else {
                    // If the parent class loader is null, it is already an Ext loader
                    // Delegate the task to the Bootstrap loaderc = findBootstrapClassOrNull(name); }}catch (ClassNotFoundException e) {
                // Handle an exception, throw an exception
            }

            if (c == null) {
                // If none is found, use a custom implementation of findClass
                // go to find and load
                long t1 = System.nanoTime();
                c = findClass(name);

                // This is to record the data related to class loading (e.g. time, number of classes loaded, etc.)sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); }}// Whether parsing is required at load time, and if so, parsing is triggered
        if (resolve) {
            resolveClass(c);
        }
        // Returns the Class object generated after loading
        returnc; }}// ClassLoader → findClass() method
protectedClass<? > findClass(String name)throws ClassNotFoundException {
    // Throw an exception directly (this method is left to subclasses to override)
    throw new ClassNotFoundException(name);
}

// ClassLoader class → defineClass() method
protected finalClass<? > defineClass(String name,byte[] b,
        int off, int len) throws ClassFormatError
{
    // Call the defineClass method,
    // Convert the contents of byte array B to a Java class
    return defineClass(name, b, off, len, null);
}

// ClassLoader → resolveClass() method
protected final void resolveClass(Class
        c) {
    // Call the local (navite) method to parse a class
    resolveClass0(c);
}

// ClassLoader class → getParent() method
@CallerSensitive
public final ClassLoader getParent(a) {
    // If the parent of the current class loader is empty, null is returned
    if (parent == null)
        return null;
    // If not empty, get the security manager first
    SecurityManager sm = System.getSecurityManager();
    if(sm ! =null) {
        // Then check permissions and return the parent of the current classLoader
        checkClassLoaderPermission(parent,
                Reflection.getCallerClass());
    }
    return parent;
}
Copy the code

A brief list of the key methods of the ClassLoader class is as follows:

  • loadClass(name,resolve): Indicates the loading namenameClass, and returns after loadingClassObject instance
  • findClass(name): The search name isnameClass, the return is aClassObject instance (this method is left to subclass override, inloadClassIn case the parent class loader fails to load, this method will be called to complete the class loading, so as to ensure that the custom class loader also conforms to the parent delegate mode.
  • defineClass(name,b,off,len): stream bytesbConvert to aClassobject
  • resolveClass(c): Use this method to load the generatedClassObject is parsed simultaneously
  • getParent(): Gets the parent of the current class loader

OK, after a brief look at the relationship between class loaders in Java, how can we analyze the implementation of parental delegation? The ExtClassLoader does not override the loadClass() method. The AppClassLoader overrides the loadClass() method. Internally, however, it still calls the loadClass() method of its parent, as follows:

// sun.misc.Launcher class → AppClassLoader inner class → loadClass() method
 public Class loadClass(String name, boolean resolve)
     throws ClassNotFoundException
 {
     int i = name.lastIndexOf('. ');
     if(i ! = -1) {
         SecurityManager sm = System.getSecurityManager();
         if(sm ! =null) {
             sm.checkPackageAccess(name.substring(0, i)); }}// The parent loadClass() method is still called
     return (super.loadClass(name, resolve));
 }
Copy the code

So neither ExtClassLoader nor AppClassLoader itself breaks the parent-delegate logic defined in the classLoader.loadClass () method, JVM class loaders such as Bootstrap, Ext, and App all adhere to the parental delegation model by default.

Four, custom class loader analysis and actual combat

As you can see from the previous analysis, if you want to customize a ClassLoader, you just need to inherit the ClassLoader class, but you need to override the findClass() method and write the load logic yourself. Therefore, if the general requirements are not too complex, you can directly inherit the URLClassLoader class, can omit their own findClass method and file load into bytecode stream steps, making the custom class loader writing more concise. When do we need a custom class loader?

  • (1) whenclassThe file is notclasspathPath, you need to customize the class loader to load the specified pathclass
  • (2) when aclassThe file is transmitted over the network and encryptedclassWhen a file is decrypted and then loaded into memory, a custom class loader is required
    • Case in point: For a project I wrote earlier, there was a requirement for an o&M sub-platform to write Java code on a terminal, which in this case would be written in the o&M platformclassFiles are transferred over the network and their classes are loaded
  • ③ A custom class loader is needed to dynamically change a block of code when the online environment cannot be shut down
    • For example, when hot deployment is needed (a class file generates different class objects through different class loaders to achieve hot deployment)

4.1. Custom classloader

Case: The operation and maintenance sub-platform needs a terminal to write Java code. In this case, the class file written on the operation and maintenance platform needs to be encrypted, transmitted over the network, and then the bytecode data of its class is decrypted before loading. The source code is as follows:

// Maintain the terminal class loader
public class OpsClassLoader extends ClassLoader {

    // The local storage location of the received class file
    private String rootDirPath;

    / / the constructor
    public OpsClassLoader(String rootDirPath) {
        this.rootDirPath = rootDirPath;
    }
    
    // A method to read the Class byte stream and decrypt it
    private byte[] getClassDePass(String className) throws IOException {
        String classpath = rootDirPath + className;

        // Simulate the file reading process.....
        FileInputStream fis = new FileInputStream(classpath);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int bufferSize = 1024;
        int n = 0;
        byte[] buffer = new byte[bufferSize];
        while((n = fis.read(buffer)) ! = -1)
            // Simulate data decryption process.....
            baos.write(buffer, 0, n);
        byte[] data = baos.toByteArray();

        // Simulation saves decrypted data....
        return data;
    }

    // Overrides the parent's findClass method
    @SneakyThrows
    @Override
    protectedClass<? > findClass(String name)throws ClassNotFoundException {
        // Read the specified class file
        byte[] classData = getClassDePass(name);
        // If no data is read, throw an exception that does not exist
        if (classData == null)
            throw new ClassNotFoundException();
        // Generate Class objects by calling the defineClass method
        return defineClass(name,classData,0,classData.length); }}Copy the code

In the above source code, we use getClassDePass() to read the byte stream data from the network and store it in the local class file, and do the corresponding decryption processing (simulation) to read the data, and then rewrite the parent class classLoader.findClass () method. The final Class object is generated in JVM memory using the defineClass() method.

Of course, if you want to make your code more concise, you can also do this by inheriting the URLClassLoader class.

4.2 Analysis of hot deployment mechanism

We are all familiar with hot deployment. In the absence of hot deployment, we often had to restart the entire project to make a small change to Java code. This is no doubt very painful, especially in the early days of Eclipse and MyEclipse editor development, many times because you wrote the code did not press Ctrl+S to save the code, rushed to debug your project, only to find that you did not save your new code, and need to save again after restarting the project. This is a bad experience that most older developers have experienced.

So before the advent of hot deployment, the era of rebooting was a frustrating experience, especially for big projects that took hours to get started. After the emergence of hot deployment mechanism, we can find that when we run a Java program, dynamically modify the code of a class after saving, the program will automatically load the updated code, how is this implemented? In before the class loading mechanism, we analyzed that fully qualified name of the same after a class is loaded, the second time to use the class, will be directly in the class loader namespace (in) can be understood as a cache lookup, without secondary loading such, forced to specify the same class loader secondary load the same class, will throw an exception. So once a class is loaded, even if the class file of a class changes, the JVM does not load it again.

The implementation of the so-called hot deployment mechanism is actually relatively simple, by using different class loaders to load the changed class file, thereby creating two different class objects in memory. So as to achieve the purpose of class file changes can be effective.

Parent delegate destroyer – thread context classloader

In Java, we are officially provided with many SPI interfaces, such as JDBC, JBI, JNDI, etc. This KIND of SPI interface, the official will usually only define the specification, the specific implementation is completed by a third party, such as JDBC, different database vendors need to implement according to the DEFINITION of JDBC interface.

These SPI interfaces are provided directly by the Java core libraries, typically in rT.jar packages, while the code libraries for third-party implementations are typically placed in the classpath path. And here’s the question:

The SPI interface in the rt.jar package is loaded by the Bootstrap class loader, while the SPI implementation classes in the classpath path are loaded by the App class loader. However, the implementer’s code is often called in SPI interfaces, so it is usually necessary to load its own implementation class first, but the implementation class is not within the loading scope of the Bootstrap class loader. After the previous analysis of the parent delegate mechanism, we have learned that: A subclass loader can delegate classloading requests to a parent class loader, but this process is irreversible. The parent class loader cannot delegate class loading requests to its own subclass loader, so the question arises: how do I load the implementation class of the SPI interface? The answer is to break the parental delegation model.

Service Provider Interface (SPI) : Java SPI mechanism, which is pluggable. In a system, often can be divided into different modules, such as log module, JDBC module, etc., and each module are general scheme, there are many if in the core libraries of the Java, written in the form of a hard-coded directly dead implementation logic, so if you want to change another implementation scheme, it need to modify the core library code, this is violating the principle of mechanism can pull plug. To avoid such problems, a dynamic service discovery mechanism is needed that dynamically detects implementers during application startup. SPI provides a mechanism for finding a service implementation for an interface. As follows:

When a third party implementer provides an implementation of the service interface, a file named after the service interface is created in the META-INF/services/ directory of the JAR package. This file is the implementation class that implements the service interface. When an external application assembs the module, it can find the implementation class name in the meta-INF /services/ jar file and load the instantiation to complete the module injection. Based on such a convention, it is easy to find the implementation class of the service interface without having to specify it in the code. The JDK also provides a utility class for finding service implementors: java.util.serviceloader.

Thread-context classloaders are disruptors of the parent delegate model. They can break the load chain of the parent delegate mechanism in the execution thread, so that the program can use the class loader in reverse. How does a thread-context class loader break the parent-delegate model so that a program can use the class loader backwards? Next, take a look at the source code of the JDBC driver.

5.1. Analyze thread context classloaders from JDBC perspective

Let’s start by looking at one of the core classes defined by SPI in Java: DriverManager (rt.jar) is a Java Driver used to manage different database vendors. The Driver classes implemented by these vendors all inherit from Java’s core java.sql.Driver. For example, the Driver class of MySQL com.mysql.cj.jdbc.driver. DriverManager (DriverManager) :

// Rt. jar package → DriverManager class
public class DriverManager {
	/ /...
	
	// Static code block
    static {
        // Load and initialize the driver
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

// DriverManager → loadInitialDrivers()
 private static void loadInitialDrivers(a) {
    // Read the system property jdbc.drivers
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run(a) {
                return System.getProperty("jdbc.drivers"); }}); }catch (Exception ex) {
        drivers = null;
    }
    
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run(a) {
            // Use the ServiceLoader class to locate the driver class file and load it
            ServiceLoader<Driver> loadedDrivers =
            ServiceLoader.load(Driver.class);
            / / omit...}});/ / omit...
}
Copy the code

Observe the above source code inDriverManagerClass in a static code blockloadInitialDrivers()Method, which will pass throughServiceLoaderFind the implementation class of the service interface. The previous analysis of JavaSPIAs mentioned before: JavaSPIThere is a dynamic service discovery mechanism that is automatically removed when the program is startedjarIn the packageMETA-INF/services/The directory looks for files named after the service,Mysql connector - Java - 6.0.6. JarThe package directories are as follows:



Looking at the engineering structure above, we can definitely see that in MySQLjarOne exists in the packageMETA-INF/services/Directory, and under that directory, there is ajava.sql.DriverFile specified in this fileMySQLdriveDriverClass path, class source code as follows:

/ / com. Mysql. Cj). The JDBC Driver class
public class Driver extends NonRegisteringDriver 
                        implements java.sql.Driver {
    public Driver(a) throws SQLException {}/ / to omit...
}
Copy the code

As you can see, this class implements java.sql.Driver, the SPI interface defined by Java, so at startup, SPI’s dynamic service discovery mechanism can discover the Driver class at the specified location.

Com.mysql.jdbc.driver is discarded in jar packages after MySQL6.0 and replaced with com.mysql.cj.jdbc.driver. Because the latter does not need to register the Driver manually through class.forname (” com.mysql.jdbc.driver “), all can be handed over to the SPI mechanism.

Serviceloader.load () : serviceloader.load () :

// ServiceLoader class → load()
public static <S> ServiceLoader<S> load(Class<S> service) {
    // Get the thread context class loader
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // Use the thread context class loader to load the driver class
    return ServiceLoader.load(service, cl);
}
Copy the code

The implementation class of the SPI interface is loaded by the Thread context classloader of the current executing Thread, which is obtained through Thread.currentThread().getContextClassLoader(). The Ext and App class loaders are both internal classes of the Launcher class, and the initialization of the Ext and App class loaders is done in the Launcher constructor. The following code is executed:

Thread.currentThread().setContextClassLoader(loader);

In the constructor of the Launcher, the AppClassLoader system class loader that you have created is set as the default thread-context class loader.

After the analysis of this place, some friends may be a little confused, let’s sort out the overall process:

Java program startup → JVM initialization of C++ Bootstrap start class loader → Bootstrap load Java core class (core class contains the Launcher class) → Bootstrap load the Launcher class, The Launcher constructor is triggered → Bootstrap executes the logic of the Launcher constructor → Bootstrap initializes and creates Ext and App classloaders → The Launcher constructor sets Ext as the parent classloader of App → At the same time, App is set as the default thread context class loader → Bootstrap continues to load other Java core classes (e.g. SPI interface) → The SPI interface calls methods of third-party implementation classes → Bootstrap tries to load third-party implementation classes and finds that they are not in its loading range and cannot load → Depending on SPI’s dynamic service discovery mechanism, these implementation classes are handed over to the thread context class loader for loading (as described earlier, The thread context loader is set as the App class loader in the Launcher constructor) → load the third-party implementation classes through the App system class loader. It is found that these implementation classes are within the loading range of App and can be loaded. The implementation class of SPI interface is loaded…..

Loading process as above, it is obvious that we can feel that after the intervention of thread context class loader, it is easy to break the original parent delegation model. At the same time, it is because of the emergence of thread context class loader, which makes the Java class loader mechanism more flexible and convenient.

5.2 Summary of thread Context classloaders and SPI mechanisms

To put it simply, Java provides the definition of a number of core interfaces, called SPI interfaces, and SPI provides a dynamic service discovery mechanism (convention) to facilitate the loading of third-party implementation classes. Create a meta-INF /services/ directory in the project and create a file with the same name as the service interface. When the program starts, all implementation classes that meet the specification are found by convention and handed over to the thread context classloader for loading.

Summary of THE JVM class loading subsystem

At this point, the core point of the Java class loading mechanism is analyzed, but if you are interested in the class loading mechanism, you can also go to see the Spring and Tomcat class loaders, but this is mainly for the knowledge of the JVM analysis, the other source code is no longer extended, The framework source code analysis may be updated in a future article.

After all, the core of the Java class loading mechanism is also the key points mentioned in the beginning: class loading process, class loaders, parent delegate model, custom class loaders, and thread context class loaders.