An overview of the

This article will describe in detail the JVM class loading subsystem and the core principles of SPI implementation.

Class loader

From the perspective of the Java virtual machine, there are only two different class loaders: the Bootstrap ClassLoader, which is implemented in C++ and is part of the virtual machine itself; The other is all the other classloaders, which are implemented in the Java language, exist independently of the virtual machine, and all inherit from the abstract java.lang.classloader class. The class loader structure is shown in the following figure:

1. Start the class loader

Bootstrap Class Loader: This class loader is responsible for loading files stored in

\lib, or in the path specified by the -xbootclasspath parameter, that are recognized by the Java virtual machine (by filename, e.g. Rt.jar, tools.jar, Libraries with incorrect names will not be loaded even if placed in the lib directory.) The libraries are loaded into the virtual machine memory. If you want to delegate a load request to the bootloader, you can use null instead. The following is a snipfragment of code for the java.lang.class# getClassLoader() method. The comments and code implementation explicitly state the convention for null values to represent bootstrap classloaders.

//Returns the class loader for the class. Some implementations may use
//null to represent the bootstrap class loader. This method will return
//null in such implementations if this class was loaded by the bootstrap
//class loader.
public ClassLoader getClassLoader(a) {
    ClassLoader cl = getClassLoader0();
    if (cl == null)
        return null;
    SecurityManager sm = System.getSecurityManager();
    if(sm ! =null) {
        ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
    }
    return cl;
}
Copy the code

2. Extend the class loader

Extension ClassLoader: This ClassLoader is implemented as Java code in the sun.misc.Launcher$ExtClassLoader Class. It is responsible for loading all libraries in the

\lib\ext directory, or in the path specified by the java.ext.dirs system variable. According to the name “extension class loader, you can infer that the class library is a Java system of exhibition mechanism, expanding the JDK development team allows the user to place an example of the libraries in the ext directory to extend the functionality of Java SE, after the JDK 9, the extension mechanism is modular replaced the natural extension of ability. Since the extension classloader is implemented in Java code, developers can use the extension classloader directly in their programs to load Class files. If it is its file location JDK/SRC/share/classes/sun/misc/Launcher. Java

3. Apply class loaders

Application ClassLoader: This ClassLoader is implemented by sun.misc.Launcher$AppClassLoader. Because the application ClassLoader is the return value of the getSystemClassLoader() method in the ClassLoader class, it is also called the “system ClassLoader” in some cases. It is responsible for loading all libraries on the user’s classpath, and developers can also use the classloader directly in their code. If the application does not have its own custom class loader, this is generally the default class loader in the application, and can also be specified through java.class.path.

4. Customize the loader

Inheriting the java.lang.ClassLoader class, I’ll give you a simple example below

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

What is parental delegation

If a class loader receives a request to load a class, the class loader does not load the class. Instead, it delegates the request to the parent class loader, at every level of class loader, so that all classloading requests are eventually passed to the top starting class loader; Only when the parent class loader fails to find the desired class in its search scope and reports the result back to the subclass loader will the subclass loader attempt to load it itself.

Summary: In the process of class loading, it will not load the class itself. It will try to get the parent class to load. If the parent class cannot load, it will try to load the class itself. The goal is to ensure that the same class can always be loaded by the same classloader.

Break parental delegation

In the process of parental delegation, there is a problem that Java defines a lot of interface information in rt.jar package, which needs to be implemented by the third party manufacturers or users themselves. In this way, the implementation class cannot be found. If these two problems need to be solved, we can use SPI scheme to deal with them.

1. Custom class loaders

Inheriting the java.lang.ClassLoader class, I’ll give you a simple example below

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

2. The mechanism of SPI

Is a service discovery mechanism. It automatically loads the classes defined in the files by looking for them in the META-INF/services folder in the ClassPath path. This mechanism provides the possibility for many framework extensions, such as the SPI mechanism used in Dubbo and JDBC. Take mysql-connector-Java as an example

  1. The following figure shows the files in the services directory. The file name is the name of the JDBC driver permission setting interface provided by the JDKjava.sql.Driver

2. Load com.mysql.jdb.Driver. Let’s start with a simple invocation example

public class JdbcTest {

    public static void main(String[] args) throws SQLException {
        Connection con = DriverManager.getConnection(
                "JDBC: mysql: / / 127.0.0.1:3306 / SSM"."root"."root123");
        PreparedStatement pds = con.prepareStatement("select 1");
        ResultSet rs = pds.executeQuery();
        while (rs.next()) {
            String result = rs.getString(1);
            System.out.println("rs: "+ result); } rs.close(); con.close(); }}Copy the code
  1. The core class for getting links isDriverManagerLet’s look at the spi operations it providesloadInitialDriversThe process at the heart of this approach isServiceLoader.load(Driver.class)A call to the Serviceloader. load method, which is implemented by looking up all the dependent jars in the interface classDriverClass SPI definition, and finally go throughdriversListcallClass.forNameThe tripartite implementation classes are loaded through the context class loader.
private static void loadInitialDrivers(a) {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run(a) {
                    return System.getProperty("jdbc.drivers"); }}); }catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run(a) {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated. * It may be the case that the driver class may not be there * i.e. there may be a packaged driver with the service class * as implementation of java.sql.Driver but the actual class * may be missing. In that case a java.util.ServiceConfigurationError * will be thrown at runtime by the VM trying to locate * and load the service. * * Adding a try catch block to catch those runtime errors * if driver not available in classpath but it's * packaged as service and that service is there in classpath. */
                try{
                    while(driversIterator.hasNext()) { driversIterator.next(); }}catch(Throwable t) {
                // Do nothing
                }
                return null; }}); println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: "+ ex); }}}Copy the code

Thread context class loader

Thread Context ClassLoader This classloader can be set using the setContextClassLoader() method of the java.lang.Thread class. If the current line is not set, it will inherit one from the parent Thread. If it is not set globally in the application, The class loader is the application class loader by default. The thread-context class loader can be used to load the required SPI service code. This is an act of the parent class loader asking the subclass loader to complete the class loading, which actually breaks the hierarchy of the parent delegate model and reverse-uses the class loader. The java.util.ServiceLoader class was provided after JDK 1.6 to provide an SPI class loading model in the form of a chain of responsibility with configuration information in meta-INF /services.