The Java class loader is the bridge between the user program and the JVM virtual machine. It plays a crucial role in Java programs, and understanding it helps us write more elegant programs. This article first introduces the Java virtual machine loader process, briefly describes the Java class loader loading mode (parental delegation mode), then introduces several common class loaders and their application scenarios, and finally shows an example of how to customize class loaders. Many places in this article refer to the Java official documentation for tutorials on virtual machine loading

The basic concept

Basic file types and concepts

Common concepts:

  1. Java source file (.java) :. Java is the suffix of the Java source file, which stores the function code written by the programmer. It is only a text file and cannot be recognized by the Java VIRTUAL machine.

  2. Java bytecode files (.class) : Java files can be compiled using the javac command (a tool provided by the JDK itself), which are essentially binary files that can be loaded (classloaded) by the Java virtual machine and then executed into The Java interpretation, that is, run your program. Java bytecode files (.class files) seem redundant, so why can’t the Java virtual machine just execute the Java source code? The Java virtual machine itself only recognizes. Class files, so any language (Python, Go, etc.) can be executed on the Java Virtual machine as long as it has an appropriate interpreter to interpret it as a. Class file. The following is the Java official description of the relationship between Class files and virtual machines.

    The Java Virtual Machine knows nothing of the Java programming language, only of a particular binary format, the class file format. A class file contains Java Virtual Machine instructions (or bytecodes) and a symbol table, as well as other ancillary information. For the sake of security, the Java Virtual Machine imposes strong syntactic and structural constraints on the code in a class file. However, any language with functionality that can be expressed in terms of a valid class file can be hosted by the Java Virtual Machine. Attracted by a generally available, machine-independent platform, implementors of other languages can turn to the Java Virtual Machine as a delivery vehicle for their languages.

  3. Java Virtual Machine: The Java Virtual Machine (JVM for short) recognizes only. Class files. You can load the. Class files into memory to generate corresponding Java objects. There are memory management, program optimization, lock management and other functions. All Java programs eventually run on the JVM. The following is the official description of the Java VIRTUAL machine

    The Java Virtual Machine is the cornerstone of the Java platform. It is the component of the technology responsible for its hardware- and operating systemindependence, the small size of its compiled code, and its ability to protect users from malicious programs. The Java Virtual Machine is an abstract computing machine. Like a real computing machine, it has an instruction set and manipulates various memory areas at run time. It is reasonably common to implement a programming language using a virtual machine;

Idea program example

The following will use the Java project example in IDEA to demonstrate the Java source program, Java bytecode and class instance respectively:

The idea – Java source files

In general, Java programs written in IDEA belong to Java source programs, and IDEA hides the [. Java] suffix from files. We can also write and generate [.java] files using any text editor. The following figure shows a typical JAVA file

The idea – Java bytecode

Java files are not recognized by the Java virtual machine and must be translated into bytecode files to be accepted by the Java virtual machine. The process of interpreting source files as bytecodes (essentially through the Javac tool in Java) can be implemented by clicking the Build project button directly in IDEA.

The idea – class loading

Create a Java main class in IDEA, and trigger the loading process of the test class in the main class (for example, new a test class). You can view the information of the loaded class through breakpoints.

Introduction to class loaders

The role of the class loader

As you can see from the flow chart above, the classloader is responsible for reading the Java bytecode (.class file) and converting it into an instance of the Java.lang.class class. Each such instance is used to represent a Java class. An object of this class is created using the newInstance() method of this instance. The actual situation may be more complex, as Java bytecode may be dynamically generated by a tool or downloaded over a network.

The virtual machine design team implemented the class-loading action of “getting the binary stream describing a class by its fully qualified name” outside the Java virtual machine so that the application could decide how to get the classes it needed. The code module that implements this action is called a class loader.

The timing of class loading

Java class loading using dynamic class loading mechanism, the program will not load all the class files to be used by the program at the time of startup, but according to the needs of the program, through the Java ClassLoader to dynamically load a class file into the memory. Thus, class files cannot be referenced by other classes until they have been loaded into memory. When the JVM runs, the initial class is loaded, and then its associated classes are triggered from the initial class link.

Note: The “reference” in the figure refers to triggering a class load, which can be triggered in one of the following ways:

  1. Create an instance of a class that accesses static variables of the class. (Note: class initialization is not triggered when static and final modified variables of the class are accessed.) Or assign a value to a static variable.

  2. Calling a static method of a class (note: calling a static and final member method triggers class initialization! Always distinguish them from static and final variables!

  3. When a reflection call is made to a class using the java.lang.Reflect package’s methods, initialization needs to be triggered if the class has not already been initialized. Such as: Class class.forname (” bacejava. Langx “);

  4. Note that getting a class file object by class name.class does not trigger the class to be loaded. Initialize a subclass of a class

  5. Run a main class directly using the java.exe command. (To run java.exe, you essentially call the main method, so you must have the main method.)

    Java’s official description of class loading: The Java Virtual Machine starts up by creating an initial class or interface using the bootstrap class loader or a user-defined class loader . The Java Virtual Machine then links the initial class or interface, initializes it, and invokes the public static method void main(String[]). The invocation of this method drives all further execution. Execution of the Java Virtual Machine instructions constituting the main method may cause linking (and consequently creation) of additional classes and interfaces, as well as invocation of additional methods. The initial class or interface is specified in an implementation-dependent manner. For example, the initial class or interface could be provided as a command line argument. Alternatively, the implementation of the Java Virtual Machine could itself provide an initial class that sets up a class loader which in turn loads an application. Other choices of the initial class or interface are possible so long as they are consistent with the specification given in the previous paragraph.

Meaning of class loaders

Class loaders are an innovation of the Java language and an important reason for its popularity. It enables Java classes to be dynamically loaded into the Java Virtual machine and executed. Classloaders have been around since JDK 1.0 and were originally developed to meet the needs of Java applets. Java applets need to remotely download Java class files to the browser and execute them. Classloaders are now widely used in Web containers and OSGi. In general, developers of Java applications do not need to interact directly with class loaders. The default behavior of the Java VIRTUAL machine is sufficient for most situations. However, if you have a situation where you need to interact with a class loader and you don’t understand the mechanics of the classloader, you can easily spend a lot of time debugging exceptions like ClassNotFoundException and NoClassDefFoundError.

The basic flow of class loading

1. Loading: Loading is done through classloaders, which can either be used as preloading or as lazy loading at runtime.

2. Verification: Ensure that the byte stream in the. Class file meets the requirements of the current VM and does not compromise VM security. Whether the validation stage is rigorous directly determines whether the Java VIRTUAL machine can withstand malicious code attacks. On the whole, the verification phase will roughly complete the following four stages of verification: file format verification, metadata verification, bytecode verification, symbol reference verification.

3. Preparation: The main tasks in the preparation phase are as follows: Allocating memory for class variables; Sets the initial value of a class variable

4. Parsing: The parsing phase is the process in which the VIRTUAL machine replaces symbolic references in the constant pool with direct references

5. Initialization: The initialization phase is when the virtual machine executes the class constructor < Clinit >() method.

6. Use: Use the class information normally

7. Unload: When a class unload condition (which is harsh) is met, the JVM removes the corresponding class information from memory

The Oracle website only roughly divides class loading into three stages: loading (including loading, validation, and preparation), linking, and initialization. The following is the Java official description of class loading

The Java Virtual Machine dynamically loads, links and initializes classes and interfaces. Loading is the process of finding the binary representation of a class or interface type with a particular name and creating a class or interface from that binary representation. Linking is the process of taking a class or interface and combining it into the run-time state of the Java Virtual Machine so that it can be executed. Initialization of a class or interface consists of executing the class or interface initialization method <clinit>

Class loaders in detail

Three ways to generate class objects

Oracle classifies class loaders into two types: bootstrapclassloaders and user-defined classloaders. User-defined classloaders inherit from the ClassLoad class. The startup class loader is used to load some core Java libraries, such as rt.jar. Custom loaders can load class files from a variety of sources. The following is the Java official description of how class loaders are generated. >There are two kinds of class loaders: the bootstrap class loader supplied by the Java Virtual Machine, and user-defined class loaders.Every user-defined class loader is an instance of a subclass of the abstract class ClassLoader. Applications employ user-defined class loaders in order to extend the manner in which the Java Virtual Machine dynamically loads and thereby creates classes. User-defined class loaders can be used to create classes that originate from user-defined sources. For example, a class could be downloaded across a network, generated on the fly, or extracted from an encrypted file.

The array itself is also an object, but the corresponding class of this object is not loaded by the class loader, but generated by the JVM. Array classes do not have an external binary representation; they are created by the Java Virtual Machine rather than by a class loader.

To sum up: there are three ways to generate a class:

  1. Start the class loader

  2. User-defined class loaders

  3. The JVM generates array objects

    The Java Virtual Machine uses one of three procedures to create class or interface C denoted by N: • If N available space in space or an interface, one of the two following methods is used to load and thereby create C: – If D was defined by the bootstrap class loader, Then the bootstrap class loader initiates loading of C. – If D was defined by a user-defined class loader, Then that same user-defined class loader initiates loading of C. • Otherwise N available space an array class. An array class is created directly by the Java Virtual Machine, not by a class loader. However, the defining class loader of D is used in the process of creating array class C.

Start the class loader

The startup class loader loads the classes required by the JVM itself. This class load is implemented in C++ and is part of the virtual machine itself. It is responsible for loading the core libraries in the <JAVA_HOME>/lib path or jar packages in the path specified by -xbootclasspath. Notice The VM loads jar packages based on the file name, such as rt.jar. If the file name is not recognized by the VM, throwing the JAR package to the lib directory is useless. For security reasons, Bootstrap starts the class loader to load only classes whose package names start with Java, Javax, and Sun. In the parent delegate model, if the parent of a class loader is null, it means that the parent of the class loader is the initiator class loader

Bootstrap class loader. It is the virtual machine’s built-in class loader, typically represented as null, and does not have a parent. The following steps are used to load and thereby create the nonarray class or interface C denoted by N using the bootstrap class loader. First, the Java Virtual Machine determines whether the bootstrap class loader has already been recorded as an initiating loader of a class or interface denoted by N. If so, this class or interface is C, and no class creation is necessary. Otherwise, the Java Virtual Machine passes the argument N to an invocation of a method on the bootstrap class loader to search for a purported representation of C in a platform-dependent manner. Typically, a class or interface will be represented using a file in a hierarchical file system, and the name of the class or interface will be encoded in the pathname of the file. Note that there is no guarantee that a purported representation found is valid or is a representation of C. This phase of loading must detect the following Error: • If no purported representation of C is found, loading throws an instance of ClassNotFoundException.

User-defined class loaders

User-defined class loaders can be divided into two types:

  1. Platform class loaders and application class loaders in Java libraries
  2. User-written class loaders, such as mechanisms for loading classes over the network

Array class loader

The Class of an array is generated by the JVM, but the class.getClassLoader () of the array Class is identical to the classloader of the array elements, which is empty if the elements of the array are primitive.

Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.

Introduction to user-defined class loaders

This section takes a closer look at the class loaders shown below:

Basic ClassLoader ClassLoader

Reference: docs.oracle.com/en/java/jav…

The ClassLoader class is the base class for all class loaders. The basic responsibility of the ClassLoader Class is to find or generate bytecode for a given Class name, and then define a Java Class from that bytecode, that is, an instance of java.lang.Class. In addition, the ClassLoader is responsible for loading the resources required by the Java application, such as image files and configuration files. However, this section only discusses its ability to load classes. To fulfill this responsibility, ClassLoader provides a series of methods, the most important of which are shown in the java.lang.ClassLoader class introduction. Details about these methods are described below.

A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system. Every Class object contains a reference to the ClassLoader that defined it.

This default support concurrent load, can pass this registerAsParallelCapable method take the initiative to cancel the concurrent load operation, this concurrency loading principle is as follows: When ClassLoader loads a class, if the class is being loaded for the first time, it takes the fully qualified name of the class as the Key, and a new Object() as the Value, and stores it into a ConcurrentHashMap. The object is used as the lock for synchronization control. If another thread requests to load the class again at the same time, the object in the map is retrieved and the object is found to be occupied, which blocks. That is, concurrent loading of the ClassLoader is implemented using a ConcurrentHashMap.

    // The process of obtaining a lock when Java loads a class
    protected Object getClassLoadingLock(String className) {
        // If concurrent loading is not enabled, use the ClassLoader object itself to lock
        Object lock = this;
        // When concurrent loading is enabled, the class object to be loaded is obtained from ConcurrentHashMap and locked.
        if(parallelLockMap ! =null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) { lock = newLock; }}return lock;
    }
Copy the code

In some not strictly follow parents delegation model scenarios, concurrent load may cause class loader deadlock: for example: two classes A and B using different class loaders, A class of static initialization code block contains initialization class B (new) B, B type initialization code block also contains A class initialization (new A); If A and B are loaded concurrently, deadlock situations can occur. Moreover, locking occurs at the JVM level, and deadlocks cannot be seen using common Java class loading tools.

Class loaders that support concurrent loading of classes are known as parallel capable class loaders and are required to register themselves at their class initialization time by invoking the ClassLoader.registerAsParallelCapable method. Note that the ClassLoader class is registered as parallel capable by default. However, its subclasses still need to register themselves if they are parallel capable. In environments in which the delegation model is not strictly hierarchical, class loaders need to be parallel capable, otherwise class loading can lead to deadlocks because the loader lock is held for the duration of the class loading process (see loadClass methods).

methods instructions
getParent() Returns the parent class loader of the class loader (used in the parent delegate model described below).
findClass(String name) Find the Class named name and return an instance () of the java.lang.class Class.
loadClass(String name) Load a Class named name and return an instance of the java.lang.Class Class. LoadClass differs from findClass in that it adds parent delegates and judgments
findLoadedClass(String name) Find the loaded Class named name and return an instance of the java.lang.Class Class.
defineClass(String name, byte[] b, int off, int len) Convert the contents of byte array B to a Java Class, and the result is an instance of the java.lang.Class Class. This method is declared final
resolveClass(Class<? > c) Link to the specified Java class.

The actual class loading is done by calling defineClass; The loading process of the startup class is implemented by calling loadClass. The first is called the defining loader of a class, while the latter is the initiating loader. When the Java virtual machine determines whether two classes are the same, it uses the class definition loader. That is, it doesn’t matter which classloader initiates the loading process for the class, but what matters is the loader that ultimately defines the class. The two types of loaders are related in that the definition loader of one class is the initial loader of the other classes it references. Outer refers to com.example.Inner, then com.example.Outer’s definition loader is responsible for starting the com.example.Inner loading process. Methods loadClass () throws is Java. Lang, a ClassNotFoundException exception; Method defineClass () throws is Java. Lang. NoClassDefFoundError exception. When a Class is successfully loaded, the Class loader caches the resulting instance of the Java.lang. Class Class. The next time the class is loaded, the class loader uses the cached instance of the class without attempting to load it again. That is, classes with the same full name are loaded only once for a classloader instance, meaning that the loadClass method is not called repeatedly.

Permission management class loader SecureClassLoader

Code source and security manager are added on the basis of ClassLoader.

This class extends ClassLoader with additional support for defining classes with an associated code source and permissions which are retrieved by the system policy by default.

The built-in class loader BuiltinClassLoader

(Check out the Java9 Jigsaw modularity feature.) The BuiltinClassLoader uses a different delegate model than the regular delegate model and supports loading classes and resources from modules. When a class is requested to load, the classloader first maps the class name to its package name. If there is a module defined for the BuiltinClassLoader that contains the package, the class loader delegates directly to the class loader. If there is no module containing the package, it delegates the search to the parent class loader, and if it is not found in the parent class, it searches the classpath. The main difference between this delegate model and the usual delegate model is that it allows platform classloaders to delegate to application classloaders, which is probably related to the Java9 Jigsaw modularity feature (which breaks the parent delegate model).

The delegation model used by this ClassLoader differs to the regular delegation model. When requested to load a class then this ClassLoader first maps the class name to its package name. If there is a module defined to a BuiltinClassLoader containing this package then the class loader delegates directly to that class loader. If there isn’t a module containing the package then it delegates the search to the parent class loader and if not found in the parent then it searches the class path. The main difference between this and the usual delegation model is that it allows the platform class loader to delegate to the application class loader, important with upgraded modules defined to the platform class loader.

PlatformClassLoader PlatformClassLoader

Starting with JDK9, the extension ClassLoader is renamed Platform ClassLoader, and some Java base modules that do not require AllPermission are relegated to the Platform ClassLoader, and the corresponding permissions are more fine-grained. It is used to load Java extension libraries. The Implementation of the Java Virtual machine provides an extension library directory. The class loader finds and loads Java classes in this directory.

Platform class loader. All platform classes are visible to the platform class loader that can be used as the parent of a ClassLoader instance. Platform classes include Java SE platform APIs, their implementation classes and JDK-specific run-time classes that are defined by the platform class loader or its ancestors. To allow for upgrading/overriding of modules defined to the platform class loader, and where upgraded modules read modules defined to class loaders other than the platform class loader and its ancestors, then the platform class loader may have to delegate to other class loaders, the application class loader for example. In other words, classes in named modules defined to class loaders other than the platform class loader and its ancestors may be visible to the platform class loader.

Application class loader AppClassLoader

The system classloader is responsible for loading into memory the directory indicated by the user classpath (java-classpath or -djava.class. path variable, that is, the path of the current class and the path of the third party libraries it references. If the programmer does not have a custom class loader, it is called by default.

System class loader. It is also known as application class loader and is distinct from the platform class loader. The system class loader is typically used to define classes on the application class path, module path, and JDK-specific tools. The platform class loader is a parent or an ancestor of the system class loader that all platform classes are visible to it.

User-defined class loaders

In general, user-defined classloaders override findClass in the ClassLoader base class so that findClass can read bytecode. Class files from user-specified locations. It is not recommended that you override the loadClass method because loadClass contains the parent delegate model and logic associated with locking. The parent of a user-defined ClassLoader can be specified in the constructor. If not, the getSystemClassLoader() method in ClassLoader is called to get the default ClassLoader:

    @CallerSensitive
    public static ClassLoader getSystemClassLoader(a) {
        switch (VM.initLevel()) {
            case 0:
            case 1:
            case 2:
                // the system class loader is the built-in app class loader during startup
                return getBuiltinAppClassLoader();
            case 3:
                String msg = "getSystemClassLoader cannot be called during the system class loader instantiation";
                throw new IllegalStateException(msg);
            default:
                // system fully initializedasset VM.isBooted() && scl ! =null;
                SecurityManager sm = System.getSecurityManager();
                if(sm ! =null) {
                    checkClassLoaderPermission(scl, Reflection.getCallerClass());
                }
                returnscl; }}Copy the code

Normally, the Java virtual machine loads classes from the local file system in a platform-dependent manner. However, some classes may not originate from a file; they may originate from other sources, such as the network, or they could be constructed by an application. The method defineClass converts an array of bytes into an instance of class Class. Instances of this newly defined class can be created using Class.newInstance. The methods and constructors of objects created by a class loader may reference other classes. To determine the class(es) referred to, the Java virtual machine invokes the loadClass method of the class loader that originally created the class. For example, an application could create a network class loader to download class files from a server. Sample code might look like: ClassLoader loader = new NetworkClassLoader(host, port); Object main = loader.loadClass(“Main”, true).newInstance(); . . . The network class loader subclass must define the methods findClass and loadClassData to load a class from the network. Once it has downloaded the bytes that make up the class, it should use the method defineClass to create a class instance. A sample implementation is: 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 . . . } }

Special logic for class loaders

Parental delegation model

Generally, class loading in Java adopts the parent delegation model by default, that is, when loading a class, it first determines whether its define loader has loaded this class. If it has loaded the class object, it directly obtains the class object. If it does not find the class object, it gives it to the parent class loader to repeat the above process. In Java, the loader relationship is as follows:

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will usually delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself.

The process of parental delegation is as follows:

  1. When a classloader receives a classloading task, it checks to see if it is in the cache. If not, it delegates the task to its parent loader.
  2. The parent loader does the same thing, delegating layer by layer until the topmost class loader starts.
  3. If the startup class loader does not find the class to load, it returns the loading task to the next class loader, which does the same.
  4. If the lowest class loader still does not find the required class file, it throws an exception.

If you write a class that has the same name as a class in the core library, you’ll find that the class will compile normally, but it will never be loaded and run. Because the class you’re writing isn’t going to be loaded by the application class loader, it’s going to be delegated to the top level, and it’s going to be found by the launcher class loader in the core library. Without parental delegation to ensure that classes are globally unique, anyone could write a java.lang.Object class and put it in the classpath, and the application would be a mess. From a security point of view, the Java virtual machine always looks for types from the most trusted Java core API through the parent delegation mechanism, which prevents untrusted classes from posing as trusted classes and causing harm to the system.

Context classloader

Java provides a number of Service Provider interfaces (SPIs) that allow third parties to provide implementations for these interfaces. Common SPIs are JDBC, JCE, JNDI, JAXP, and JBI. The INTERFACES to these SPIs are provided by Java core libraries, and the IMPLEMENTATION code of these SPIs is included in the CLASSPATH as jar packages that Java applications depend on. Code in SPI interfaces often needs to load concrete implementation classes. The problem is that SPI interfaces are part of the Java core library and are loaded by the Bootstrap Classloader. SPI implementation classes are loaded by the System ClassLoader **. The bootloader cannot find an implementation class for SPI because BootstrapClassloader cannot delegate an AppClassLoader to load classes according to the parent delegate model. The thread-context classloader breaks the “parent delegate model” and can discard the parent delegate chain mode in the execution thread, so that the program can use the class loader in reverse.

To put it simply: the SPI interface classes in the Java core library should be loaded by the launcher class loader, but because SPI implements the class mechanism, the SPI interface classes are loaded by the context class loader, so that the SPI interface classes and implementation classes are loaded by the same class loader.

Introduce the JDBC SPI

It’s a little hard to understand just looking at the text, but here’s the JDBC example (see the blog) :

// Load the Class into the AppClassLoader and register the driver Class
// Class.forName("com.mysql.jdbc.Driver").newInstance(); 
String url = "jdbc:mysql://localhost:3306/testdb";    
// Get the database connection from the Java library
Connection conn = java.sql.DriverManager.getConnection(url, "name"."password"); 
Copy the code

The Class. ForName statement is used to comment out the JDBC link, but the program still runs normally. This is because the SPI service loading mechanism has been supported in Java1.6 version 4.0, so you can register the mysql driver as long as the mysql jar package is in the classpath. Where is the automatic registration of mysql Driver? Focus on DriverManager. GetConnection (). We all know that calling a static method of a class initializes the class and executes its static code block. DriverManager’s static code block is:

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}
Copy the code

The code for the initialization method loadInitialDrivers() is as follows:

private static void loadInitialDrivers(a) {
    String drivers;
    try {
        // Read system properties first
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run(a) {
                return System.getProperty("jdbc.drivers"); }}); }catch (Exception ex) {
        drivers = null;
    }
    // Load the driver class through SPI
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run(a) {
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) { driversIterator.next(); }}catch(Throwable t) {
                // Do nothing
            }
            return null; }});// Continue loading the driver class in system properties
    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);
            // Use AppClassloader to load
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: "+ ex); }}}Copy the code

The Driver loading sequence of DriverManager in JDBC is as follows:

  1. Read the class name in the meta-INF /services file in SPI mode and load it using TCCL.
  2. Get the Settings through System.getProperty(“jdbc.drivers”) and load them through the System class loader. Here’s a closer look at the code loaded by SPI.

SPI in JDBC

The full name of the SPI is Service Provider Interface. It is mainly applied to vloc-defined components or plug-ins. This is explained in more detail in the java.util.ServiceLoader documentation. To summarize the idea of Java SPI mechanism, there are many different implementations of abstract modules in our system, such as logging module, XML parsing module, JDBC module and so on. In object-oriented design, we generally recommend interface programming between modules rather than hard-coding implementation classes between modules. Once a specific implementation class is involved in the code, it violates the principle of pluggability, and if an implementation needs to be replaced, the code needs to be changed. A service discovery mechanism is needed in order to realize the dynamic specification during module assembly. The Java SPI provides a mechanism for finding a service implementation for an interface. Similar to the IOC idea of moving control of assembly out of the program, this mechanism is especially important in modular design. The convention of the Java SPI is that when the service provider provides an implementation of the service interface, a file named after the service interface is also created in the META-INF/services/ directory of the JAR package. This file contains the concrete 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 provides a utility class for service implementation lookup: java.util.Serviceloader.

Following the SPI introduction above, let’s examine the JDBC SPI code:

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

try{
    while(driversIterator.hasNext()) { driversIterator.next(); }}catch(Throwable t) {
// Do nothing
}
Copy the code

Note that driversiterator.next () ends up calling the class.forname (DriverName, false, loader) method that we commented out in the first place. The code omitted for SPI is now explained, so let’s move on to the loader passed to this method.

Since the Class. ForName (DriverName, false, loader) code is in the java.util.ServiceLoader Class, and the Serviceloader. Class is loaded in the BootrapLoader, The loader passed to forName must not be a BootrapLoader. TCCL is the only way to do this, which means loading classes into TCCL that you can’t load yourself (thread.currentThread (), cheating!). . As mentioned at the end of the previous article, TCCL defaults to using the system class loader (AppClassLoader) that is currently executing the code in the application. Take a look at the code for serviceloader.load (Class), and it does:

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
Copy the code

ContextClassLoader holds the reference to AppClassLoader by default. Since it is placed in the thread at runtime, it does not matter where the current application is (BootstrapClassLoader, ExtClassLoader, etc.). You can use thread.currentThread ().getContextClassLoader() to retrieve the application classloader whenever you want to do so. So that’s pretty much explained the MECHANISM of SPI. In plain English, I (JDK) provide a convenient way for you (third party implementers) to load services (e.g. database drivers, log libraries), as long as you follow the convention (write the class name in/meta-INF), when I start up I will scan all jar packages for the class name that matches the convention, and then call forName to load. But my ClassLoader is not able to load it, so load it into the TCCL of the current thread of execution. Good, just said the Driver implementation Class is com. Mysql.. JDBC Driver. Class, its static blocks of code inside and wrote what? Is TCCL used again? Let’s move on to the next example. Com.mysql.jdbc.driver static code block to run after loading:

static {
    try {
        // The Driver is already loaded into TCCL and can be instantiated directly
        java.sql.DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!"); }}Copy the code

The registerDriver method registers the driver instance in the java.sql.DriverManager class of the system, Actually add it to a static member CopyOnWriteArrayList named registeredDrivers, to this driver registration is basically complete. More case reference blog: blog.csdn.net/yangcheng33…

conclusion

Through the above case analysis, we can summarize the application scenarios of thread context class loaders:

  1. When a higher level provides a unified interface for a lower level to implement, and a lower level class is loaded (or instantiated) at a higher level, a thread-context ClassLoader must help the higher level ClassLoader find and load the class.
  2. When this class is used to host class loading, but the ClassLoader to load this class is unknown, in order to isolate different callers, it can be hosted by the respective thread context class loaders of the callers.

3.2.3 ServiceLoader

The ServiceLoader is a tool for loading the SPI service implementation classes and can handle zero, one, or more service providers.

A facility to load implementations of a service. A service is a well-known interface or class for which zero, one, or many service providers exist. A service provider (or just provider) is a class that implements or subclasses the well-known interface or class. A ServiceLoader is an object that locates and loads service providers deployed in the run time environment at a time of an application’s choosing. Application code refers only to the service, not to service providers, and is assumed to be capable of differentiating between multiple service providers as well as handling the possibility that no service providers are located.

The application loads a given service through the static methods of the ServiceLoader. If the service provider is in another modular program, the current module must declare a service implementation class that depends on the service provider. The ServiceLoader can locate and instantiate service providers through the iterator method, and the Stream method can get a stream of providers that can be checked and filtered without instantiating them.

An application obtains a service loader for a given service by invoking one of the static load methods of ServiceLoader. If the application is a module, then its module declaration must have a uses directive that specifies the service; this helps to locate providers and ensure they will execute reliably. In addition, if the service is not in the application module, then the module declaration must have a requires directive that specifies the module which exports the service. A service loader can be used to locate and instantiate providers of the service by means of the iterator method. ServiceLoader also defines the stream method to obtain a stream of providers that can be inspected and filtered without instantiating them. As an example, suppose the service is com.example.CodecFactory, an interface that defines methods for producing encoders and decoders:

As an example, the CodecFactory is an SPI service interface. GetEncoder and getDecoder excuses are defined.

 package com.example;
 public interface CodecFactory {
     Encoder (String encodingName);
     Decoder getDecoder(String encodingName);
 }
Copy the code

The following program gets the CodecFactory service provider via an iterator:

ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
    for (CodecFactory factory : loader) {
        Encoder enc = factory.getEncoder("PNG");
        if(enc ! =null)... use enc to encode a PNG filebreak;
    }
Copy the code

In some cases, we may have many service providers, but only some of them are useful. In this case, we need to filter the service implementation classes obtained by the ServiceLoader. For example, we only need the PNG CodecFactory. We can then add a custom @png annotation to the corresponding service implementation class and filter it to get the desired service provider:

 ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
 Set<CodecFactory> pngFactories = loader
        .stream()                                              // Note a below
        .filter(p -> p.type().isAnnotationPresent(PNG.class))  // Note b
        .map(Provider::get)                                    // Note c
        .collect(Collectors.toSet());
Copy the code

Principles of SPI service design: Services should be subject to the single responsibility principle and are usually designed as interfaces or abstract classes, but concrete classes are not recommended (although they can be implemented as such). Different situations design services in different ways, but two guidelines should be followed:

  1. Services open up as many methods as possible, giving service providers more freedom to customize their own service implementations.

  2. Services should indicate whether they are directly or indirectly implementing mechanisms (such as “agents” or “factories”). When domain-specific object instantiation is relatively complex, service providers often employ indirect mechanisms such as a CodecFactory service indicating by its name that its service provider is a factory for the codecs, rather than the codecs themselves, since producing some codecs can be complex.

    A service is a single type, usually an interface or abstract class. A concrete class can be used, but this is not recommended. The type may have any accessibility. The methods of a service are highly domain-specific, so this API specification cannot give concrete advice about their form or function. However, there are two general guidelines:

    1. A service should declare as many methods as needed to allow service providers to communicate their domain-specific properties and other quality-of-implementation factors. An application which obtains a service loader for the service may then invoke these methods on each instance of a service provider, in order to choose the best provider for the application.
    2. A service should express whether its service providers are intended to be direct implementations of the service or to be an indirection mechanism such as a “proxy” or a “factory”. Service providers tend to be indirection mechanisms when domain-specific objects are relatively expensive to instantiate; in this case, the service should be designed so that service providers are abstractions which create the “real” implementation on demand. For example, the CodecFactory service expresses through its name that its service providers are factories for codecs, rather than codecs themselves, because it may be expensive or complicated to produce certain codecs.

There are two ways to declare a service implementation class:

  • Through the modular package declaration: provides com. Example. CodecFactory with com. Example. Impl. StandardCodecs; provides com.example.CodecFactory with com.example.impl.ExtendedCodecsFactory; – by specifying the path statement: meta-inf/services such as: meta-inf/services/com. Example. CodecFactory add a line: com.example.impl.StandardCodecs # Standard codecs

Develop your own class loader

In most cases, though, the classloader implementation provided by default will suffice. However, in some cases, you will need to develop your own classloader for your application. For example, your application transmits Java class bytecode over the network, which is encrypted for security purposes. At this point, you need your own class loader to read the encrypted bytecode from a network address, decrypt and validate it, and finally define the classes to run in the Java virtual machine. The next two concrete examples illustrate class loader development.

File system class loader

The first class loader is used to load Java byte code stored on the file system. The complete implementation is shown in Listing 6.

public class FileSystemClassLoader extends ClassLoader {

    private String rootDir;

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    protectedClass<? > findClass(String name)throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        else {
            return defineClass(name, classData, 0, classData.length); }}private byte[] getClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while((bytesNumRead = ins.read(buffer)) ! = -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    private String classNameToPath(String className) {
        return rootDir + File.separatorChar
                + className.replace('. ', File.separatorChar) + ".class"; }}Copy the code

As shown in Listing 6, the class FileSystemClassLoader inherits from the java.lang.classLoader class. Among the common methods of the java.lang.ClassLoader class listed in the java.lang.ClassLoader class introduction, generally speaking, a home-grown ClassLoader simply overrides the findClass(String Name) method. The java.lang.classLoader method loadClass() encapsulates the implementation of the proxy pattern mentioned earlier. The method first calls the findLoadedClass() method to check whether the class has already been loaded; If not, the parent class loader’s loadClass() method is called to try to load the class; If the parent class loader cannot load the class, the findClass() method is called to find it. Therefore, to ensure that class loaders implement proxy mode correctly, it is best to overwrite the findClass() method instead of the loadClass() method when developing your own class loaders.

The findClass() method of the FileSystemClassLoader class first looks for the bytecode file (.class file) of the class based on its full name on the hard disk and then reads the contents of the file. Finally, the bytecode is converted into an instance of the java.lang.class Class using the defineClass() method.

Network class loader

The following shows how to implement dynamic updates of components through a network class loader. The basic scenario is that Java bytecode (.class) files are stored on the server, and the client obtains the bytecode over the network and executes it. When there is a version update, you just need to replace the files saved on the server. This requirement can be easily implemented through the class loader.

The NetworkClassLoader class is responsible for downloading Java class bytecode and defining Java classes over the network. Its implementation is similar to FileSystemClassLoader. After a version of a class has been loaded through NetworkClassLoader, there are generally two ways to use it. The first approach is to use the Java reflection API. Another approach is to use interfaces. Note that classes downloaded from the server cannot be referenced directly in the client code because the class loader of the client code cannot find them. Using the Java Reflection API, you can call methods of Java classes directly. The way to use an interface is to put the classes of the interface on the client side and load different versions of the classes that implement the interface from the server. These implementation classes are used on the client side through the same interface. See download for the code for the network class loader.

After explaining how to develop your own class loader, the following illustrates the relationship between the class loader and the Web container.

Class loaders and Web containers

For Web applications running in Java EE™ containers, class loaders are implemented differently than normal Java applications. Different Web containers are implemented differently. In the case of Apache Tomcat, each Web application has a corresponding class loader instance. The class loader also uses proxy mode, except that it first tries to load a class and then proxies it to the parent class loader if it can’t find it. This is the reverse of the usual class loader order. This is the recommended practice in the Java Servlet specification, and the goal is to make the Web application’s own classes take precedence over those provided by the Web container. One exception to this proxy pattern is that Java core library classes are not included in the lookup. This is also to keep the Java core libraries type safe.

For the most part, Web application developers don’t need to worry about the details associated with class loaders. Here are a few simple rules:

Each Web application has its own Java class files and library JAR packages under web-INF /classes and Web-INF /lib directories, respectively. Java class files and JAR packages shared by multiple applications are placed in directories specified by the Web container that are shared by all Web applications. When an error occurs that the class cannot be found, check that the class loader for the current class and the context class loader for the current thread are correct.

Welcome to follow the official wechat account of the royal Fox god

This article was first published to wechat public account, all rights reserved, reprint prohibited!