JVM -- class loader; parent delegation mechanism; thread context class loader (eight)

1. Class loader

The class loader reads the bytecode file (.class file) compiled by the java compiler according to the binary name of the class, and converts it into an instance of the java.lang.Class class.

Each instance is used to represent a Java class, and jvm uses these instances to generate java objects.

Such as new a String object; reflection generates a String object, will use the String.class object of the java.lang.Class class.

Basically all class loaders are an instance of the java.lang.ClassLoader class

Three categories of class loaders

class loader load class illustrate

Start class loader (Bootstrap ClassLoader)

JAVA_HOME/jre/lib no direct access

Extension ClassLoader

JAVA_HOME/jre/lib/ext The parent is Bootstrap, displaying null

Application ClassLoader

classpath The superior is Extension
custom class loader customize The superior is Application

  

The class loader loading order is as follows

  

The core method of the class loader

method name illustrate
getParent() Returns the parent class loader of this class loader
loadClass(String name) Load the class named name and return an instance of the java.lang.Class class
findClass(String name) Find the class with the name name, and the returned result is an instance of the java.lang.Class class
findLoadedClass(String name) Find the class named name that has been loaded, and the returned result is an instance of the java.lang.Class class
defineClass(String name,byte[] b,int off,int len) According to the data in the byte array b, it is converted into a Java class, and the returned result is an instance of the java.lang.Class class

The name parameters of the above methods are binary name (binary name of the class), such as

java.lang.String <package name>.<class name>

java.concurrent.locks.AbstractQueuedSynchronizer$Node <package name>.<class name>$<internal class name>

java.net.URLClassLoader$1 <package name>.<class name>.<anonymous internal class name>

(1) Startup class loader

The startup class loader is a special piece of C++ code embedded in the jvm to load the java core class library when the jvm is running

The String.class object is loaded by the boot class loader. The specific core codes loaded by the boot class loader can be obtained by obtaining the system property whose value is "sun.boot.class.path".

The startup class loader is not written in java native code, so it is not an instance of the java.lang.ClassLoader class, and it has no getParent method

public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("com.mycompany.load.F");
        System.out.println(aClass.getClassLoader()); // AppClassLoader  ExtClassLoader
    }

output

cd D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm>java -Xbootclasspath/a:. com.mycompany.load.Load4
bootstrap F init
null

Prints null, indicating that its class loader is Bootstrap ClassLoader 

 -Xbootclasspath means set bootclasspath

Among them, /a:. means to append the current directory to the bootclasspath

Replace the core class with this method

java -Xbootclasspath:<new bootclasspath>

java -Xbootclasspath/a:<append path>

java -Xbootclasspath/p:<pre-append path>

(2) Extended class loader

The extended class loader is used to load an extended directory implemented by the jvm, and all java classes in this directory are loaded by this type of loader.

This path can be obtained by obtaining the "java.ext.dirs" system property. The extended class loader is an instance of the java.lang.ClassLoader class, and its getParent method returns the boot class loader (null is used to indicate boot class loading in the HotSpot virtual machine)

D:\Java\JavaProject\jvm-demo\myjvm\out\production\myjvm>jar -cvf my.jar com/mycompany/load/F.class
已添加清单
正在添加: com/mycompany/load/F.class(输入 = 481) (输出 = 322)(压缩了 33%)

Copy the jar package to JAVA_HOME/jre/lib/ext and execute the code again

(3) Application class loader

The application class loader is also called the system class loader. Developers can use the java.lang.ClassLoader.getSystemClassLoader() method to obtain an instance of this type of loader, hence the name of the system class loader. It is mainly responsible for loading java classes written by program developers themselves

Generally speaking, java applications are loaded with this type of loader, and the class path loaded by the application class loader can be obtained by obtaining the system property of "java.class.path" (that is, what we often call classpath). The application class loader is an instance of the java.lang.ClassLoader class, and its getParent method returns the extended class loader

(4) The namespace of the class

During the running of the program, a class is not simply defined by its binary name (binary name), but is jointly determined by its binary name and the namespace (run-time package) determined by its definition loader.

When a class with the same binary name is loaded by different definition loaders, the returned Class objects are not the same, so the objects created by different Class objects have different types.

Strange errors like  Test cannot be cast to Test  's java.lang.ClassCastException are caused by the same binary name of the class but different definition loaders in many cases

package com.mycompany.load;

import sun.misc.Launcher;

public class Load6 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader classLoader = new Launcher().getClassLoader(); //1 new一个新的类加载器
        System.out.println(classLoader);

        /*
        这是因为 1处获取的应用类加载器a和jvm用来加载Load6.class对象的应用类加载器b不是同一个实例,
        那么构成这两个类的run-time package也就是不同的。所以即使它们的二进制名字相同,
        但是由a定义的Load6类所创建的对象显然不能转化为由b定义的Load6类的实例。
        这种情况下jvm就会抛出ClassCastException
        * */
        Class<?> aClass = classLoader.loadClass("com.mycompany.load.Load6");
        Load6 load6  = (Load6)aClass.newInstance(); //2
        //Exception in thread "main" java.lang.ClassCastException: com.mycompany.load.Load6 cannot be cast to com.mycompany.load.Load6
    }
}

An exception is reported:

java.lang.ClassCastException: com.mycompany.load.Load6 cannot be cast to com.mycompany.load.Load6

This is because the application class loader a obtained at 1 and the application class loader b used by jvm to load the Load6.class object are not the same instance, so the run-time packages constituting the two classes are also different.

So even though their binary names are the same, the object created by the Load6 class defined by a obviously cannot be converted into an instance of the Load6 class defined by b. In this case jvm will throw ClassCastException

Classes with the same binary name are considered two different classes if their definition loaders are different

2. Parental delegation mechanism

The parent delegation mechanism Parent Delegation Model, also known as the parent delegation model. The so-called parent delegation refers to the rules for finding classes when calling the loadClass method of the class loader (parents, it is more appropriate to understand them as superiors, because there is no inheritance relationship between them)

(1) Class loading mechanism process

The Java compiler compiles the Java source file into a .class file, and then loads the .class file into the memory by the JVM. After the JVM loads, a Class object bytecode is obtained. With a bytecode object, you can instantiate it using

(2) Class loader loading order

  

(3) Process of Parental Appointment Mechanism

1. The loading class MyClass.class is delegated level by level from low level to high level. First, the application layer loader delegates to the extension class loader, and then the extension class delegates to the startup class loader.

(1) If the custom loader is mounted to the application class loader

(2) The application class loader delegates the class loading request to the extension class loader

(3) The extended class loader delegates the class loading request to the startup class loader

2. The startup class loader fails to load, and then the extension class loader loads, the extension class loader fails to load, and finally the application class loader loads

(1) The startup class loader looks for and loads the Class file under the loading path. If the target Class file is not found, it will be loaded by the extension class loader

(2) The extension class loader looks for and loads the Class file under the loading path. If the target Class file is not found, it will be loaded by the application class loader

(3) The application class loader looks for and loads the Class file under the loading path. If the target Class file is not found, it will be loaded by the custom loader

(4) Find and load the Class file in the directory specified by the user under the custom loader. If the target Class file is not found in the custom loading path, a ClassNotFoud exception will be thrown

3. If the application class loader cannot be found, then a ClassNotFound exception will be reported

(4) Source code analysis

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 1. 检查该类是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        // 2. 有上级的话,委派上级 loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        // 3. 如果没有上级了(ExtClassLoader),则委派
                        BootstrapClassLoader
                                c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }
                if (c == null) {
                    long t1 = System.nanoTime();
                    // 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
                    c = findClass(name);
                    // 5. 记录耗时
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

The execution flow is:

1. sun.misc.Launcher$AppClassLoader //1, start to check the loaded classes, if not

2. sun.misc.Launcher$AppClassLoader // 2 places, delegate to the superior sun.misc.Launcher$ExtClassLoader.loadClass()

3. sun.misc.Launcher$ExtClassLoader // 1, check the loaded class, if not

4. sun.misc.Launcher$ExtClassLoader // 3 places, if there is no superior, then delegate BootstrapClassLoader to search

5. BootstrapClassLoader looks for classes under JAVA_HOME/jre/lib, there is no

6. sun.misc.Launcher$ExtClassLoader // 4, call your own findClass method to find the class under JAVA_HOME/jre/lib/ext, obviously not, go back to sun.misc.Launcher$AppClassLoader // 2

7. Continue to execute to sun.misc.Launcher$AppClassLoader // 4, call its own findClass method, search under the classpath, and find it 

(5) Advantages and disadvantages of the parental delegation mechanism

advantage:

1. To ensure security, the hierarchical relationship represents the priority, that is, the loading of all classes is given priority to the startup class loader, thus ensuring the core class library class

2. Avoid repeated loading of classes. If the parent class loader has loaded it, there is no need for the child class loader to load it again, ensuring the global uniqueness of a class

shortcoming:

The delegation process of checking whether a class is loaded is one-way . Although this method is structurally clear and makes the responsibilities of each ClassLoader very clear, it will also bring about a problem, that is, the top-level ClassLoader cannot access the class loaded by the bottom-level ClassLoader. kind

Usually, the classes in the startup class loader are system core classes, including some important system interfaces, while in the application class loader, they are application classes. According to this mode, there is no problem for the application class to access the system class, but there will be problems for the system class to access the application class .

If an interface is provided in the system class, the interface needs to be implemented in the application class, and the interface is also bound with a factory method for creating an instance of the interface, and both the interface and the factory method are in the startup class loader. At this time, it will appear that the factory method cannot create an application instance loaded by the application class loader

3. Thread context class loader

The thread context class loader is used to solve the defects of the parent delegation model of the class

In Java, the official provides us with many SPI interfaces, such as JDBC, JBI, JNDI, etc. For this type of SPI interface, the official often only defines the specification, and the specific implementation is completed by a third party, such as JDBC. Different database vendors need to implement it according to the definition of the JDBC interface.

These SPI interfaces are directly provided by the Java core library, generally located rt.jarin the package, and the specific code library implemented by the third party is generally placed under classpaththe path. And here comes the question:

The SPI interface located rt.jarin the package is loaded by the Bootstrap class loader, and the classpathSPI implementation class under the path is Apploaded by the class loader .

But often in the SPI interface, the code of the implementer is often called, so generally you need to load your own implementation class first, but the implementation class is not within the loading range of the Bootstrap class loader. After the analysis of the previous parent delegation mechanism, We have already learned that the child class loader can delegate the class loading request to the parent class loader for loading, but this process is irreversible . That is, the parent class loader cannot delegate class loading requests to its own sub-class loader for loading.

At this point, this question arises: how to load the implementation class of the SPI interface? That is breaking the parental delegation model

SPI (Service Provider Interface): Java's SPI mechanism is actually a pluggable mechanism. In a system, it is often divided into different modules, such as log module, JDBC module, etc., and each module generally has multiple implementation solutions. If it is directly hard-coded in the core library of Java To implement the logic, if you want to replace another implementation, you need to modify the core library code, which violates the principle of pluggable mechanism. In order to avoid such problems, a dynamic service discovery mechanism is needed, which can dynamically detect the implementer during the program startup process. The SPI provides such a mechanism to find a service implementation mechanism for an interface. as follows:

When the 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 at the same time, and this file is the implementation class that implements the service interface. When the external program assembles this module, the specific implementation class name can be found through the configuration file in the jar package META-INF/services/, and loaded and instantiated to complete the injection of the module.

Based on such a contract, the implementation class of the service interface can be found very well, without the need to formulate it in the code.

At the same time, JDK officially provides a tool class for finding service implementers: java.util.ServiceLoader

The thread context class loader is the destroyer of the parental delegation model, which can break the loading chain relationship of the parental delegation mechanism in the execution thread, so that the program can use the class loader in reverse

(1) Thread context class loader (Context Classloader)

The thread context class loader (Context Classloader) was introduced from JDK1.2. The getContextClassLoader() and setContextClassLoader(ClassLoader cl) in the class Thread are used to obtain and set the online class loader respectively. 

If not set by setContextClassLoader(ClassLoader cl), the thread will inherit the context class loader of its parent thread.
The context class loader for the initial thread of a Java application runtime is the system class loader. Code running in a thread can load classes and resources through this class loader

It can break the parent delegation mechanism. The parent ClassLoader can use the classLoader specified by Thread.currentThread().getContextClassLoader() of the current thread to load the class, which can change that the parent ClassLoader cannot use the child ClassLoader or other ClassLoaders that do not have a direct parent-child relationship The case of loaded classes that change the parent delegate model

For SPI, some interfaces are provided by the Java core library, and the Java core library is loaded by the startup class loader, but the implementation of these interfaces comes from different jar packages (provided by the manufacturer), and the Java startup class loader It will not load jar packages from other sources, so the traditional parental delegation model cannot meet the requirements of SPI. And by setting the context class loader for the current thread, the loading of the interface implementation class can be realized by the set online class loader

Java provides the definition of many core interfaces, these interfaces are called SPI interfaces, and in order to facilitate the loading of third-party implementation classes, SPI provides a dynamic service discovery mechanism (agreement), as long as the third party writes the implementation class, Create a new directory in the project META-INF/services/and create a file with the same name as the service interface in the directory, then when the program starts, it will find all the implementation classes that meet the specifications according to the agreement, and then hand it over to the thread context class loader for processing load processing

MySQLDriver驱动类

When using JDBC, it is necessary to load the Driver driver, do not write

Class.forName("com.mysql.jdbc.Driver")
或
Class.forName("com.mysql.cj.jdbc.Driver")

It is also possible to load com.mysql.jdbc.Driver correctly 

In the jar package after MySQL6.0, the previous com.mysql.jdbc.Driver driver is abandoned, but com.mysql.cj.jdbc.Driver is used instead, because the latter does not need to be Class.forName("com.mysql.jdbc.Driver")manually registered in this way Driver, all can be handed over to the SPI mechanism for processing

When using JDBC, com.mysql.cj.jdbc.Driverthe driver class of MySQL mainly uses a core class defined by SPI in Java: DriverManager, which is located rt.jarin the package and is used to manage the drivers implemented by different database vendors in Java. At the same time, the Driverdrivers implemented by these vendors Classes, all inherit from Java's core classesjava.sql.Driver

DriverManager's class loader

System.out.println(DriverManager.class.getClassLoader());

Printing null means that its class loader is Bootstrap ClassLoader, and it will search for classes under JAVA_HOME/jre/lib, but there is obviously no mysql-connector-java-xx.xx.xx.jar package under JAVA_HOME/jre/lib 

public class DriverManager {
    // 注册驱动的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers
        = new CopyOnWriteArrayList<>();
    // 初始化驱动
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}

loadInitialDrivers() method

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    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()

        // 1、使用 ServiceLoader 机制加载驱动,即 SPI
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                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);

        // 2、使用 jdbc.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);
                // 这里的 ClassLoader.getSystemClassLoader() 就是应用程序类加载器
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

From the loadInitialDrivers in DriverManager, we can know that even if we don't use Class.forName("com.mysql.cj.jdbc.Driver"), the mysql driver can also be loaded, because later jdk uses ServiceLoader

2 is to use Class.forName to complete the loading and initialization of the class, which is associated with the application class loader, so the class loading can be successfully completed

1 is the Service Provider Interface (SPI). The agreement is as follows. Under the META-INF/services package of the jar package, the file is named after the fully qualified name of the interface, and the content of the file is the name of the implementation class.

(二)ServiceLoader

ServiceLoader is a simple mechanism for loading service providers. Usually the service provider will implement the interface defined in the service. The service provider can be installed in the extension directory on the java platform in the form of an extended jar package, and can also be added to the classpath of the application.

1. The service provider needs to provide a parameterless construction method

2. The service provider is through the corresponding provider configuration file in the META-INF/services directory, and the file name of the configuration file is composed of the package name of the service interface.

3. The provider configuration file contains the class path for implementing the service interface, and each service provider occupies one line.

4. ServiceLoader loads and instantiates providers on demand, which is lazy loading. ServiceLoader also contains a service provider cache, which stores the loaded service providers.

5. ServiceLoader will return an iterator iterator, which will return all loaded service providers

6. ServiceLoader is thread-unsafe

 Use ServiceLoader

ServiceLoader<接口类型> allImpls = ServiceLoader.load(接口类型.class);
Iterator<接口类型> iter = allImpls.iterator();
while(iter.hasNext()) {
    iter.next();
}

example:

ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()){
   Driver dirver = iterator.next();
   System.out.println(dirver.getClass()+", 类加载器:"+dirver.getClass().getClassLoader());
}
System.out.println("当前线程上线文类加载器:"+Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader类加载器:"+loader.getClass().getClassLoader());

ServiceLoader. load method

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取线程上下文类加载器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

The thread context class loader is the class loader used by the current thread. The default is the application class loader. Inside it, Class.forName calls the thread context class loader to complete the class loading. The specific code is in the internal class LazyIterator of ServiceLoader

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                        "Provider " + cn + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                        "Provider " + cn + " could not be instantiated",
                        x);
            }
            throw new Error(); // This cannot happen
        }

Can refer to

 Analysis of Java class loading subsystem, parental delegation mechanism and thread context class loader

Explanation of Thread Context Class Loader in Java

4. Custom class loader

1. Custom class loader scenario

1. Load class files in non-classpath arbitrary paths

2. Implemented through the interface, when decoupling is desired (commonly used in framework design)

3. Classes with the same name in different applications can be loaded without conflicts, common in tomcat containers

2. Custom class loader steps

1. Inherit the ClassLoader parent class

2. Comply with the parent delegation mechanism and rewrite the findClass method

Note: It is not to rewrite the loadClass method, otherwise the parent delegation mechanism will not be used

3. Read the bytecode of the class file

4. Call the defineClass method of the parent class to load the class

5. The user calls the loadClass method of the class loader

public class Load7 {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> c1 = classLoader.loadClass("TestServiceImpl");
        Class<?> c2 = classLoader.loadClass("TestServiceImpl");
        System.out.println(c1 == c2);//true

        MyClassLoader classLoader2 = new MyClassLoader();
        Class<?> c3 = classLoader2.loadClass("TestServiceImpl");
        //虽然相同类名,但不是同一个类加载器加载的
        System.out.println(c1 == c3);//false

        c1.newInstance();
    }
}

class MyClassLoader extends ClassLoader {

    @Override // name 就是类名称
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = "D:\\myclasspath\\" + name + ".class";

        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Files.copy(Paths.get(path), os);

            // 得到字节数组
            byte[] bytes = os.toByteArray();

            // byte[] -> *.class
            return defineClass(name, bytes, 0, bytes.length);

        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到", e);
        }
    }
}

Guess you like

Origin blog.csdn.net/MinggeQingchun/article/details/127230676