Java class loader-what does it help us to analyze from the underlying source code?

table of Contents

Background knowledge supplement

Loading overview    

One, the basic concept of class loader

1.1 The class loader loads Class roughly through the following 8 steps

1.2 The class loading mechanism of JVM mainly has the following 3 kinds

1.3 Here is an explanation of the parent delegation mechanism

Two, start the class loader

Three, extended class loader

Four, Parental Delegation-Source Code Analysis 1

Five, Parental Delegation-Source Code Analysis 2

Six, thread context class loader

Seven, custom class loader


Background knowledge supplement

From the life cycle of a Java class, a class includes the following stages:

Class loading stage classification: loading , linking (verification/preparation/analysis), initialization (<cinit>()V method/time of occurrence), as shown in the following figure:

Reference link: Java class loading-how is the bottom layer implemented?

Loading overview    

Loading refers to reading the class file of a class into memory and creating a java.lang.Class object for it, that is, when any class is used in the program, the system will create a java.lang.Class object for it .

The loading of classes is done by a class loader . The class loader is usually provided by the JVM. These class loaders are also the basis for all previous programs to run. The class loaders provided by the JVM are usually called system class loaders. In addition, developers can create their own custom class loader by inheriting the ClassLoader base class.


One, the basic concept of class loader

The class loader is an innovation of the Java language and one of the important reasons for the popularity of the Java language. It allows Java classes to be dynamically loaded into the Java virtual machine and executed.

As the name suggests, the class loader is used to load Java classes into the Java virtual machine. Generally speaking, the Java virtual machine uses Java classes in the following way: Java source programs (.java files) are converted into Java byte codes (.class files) after being compiled by a Java compiler. The class loader is responsible for reading the Java byte code and converting it into  java.lang.Class an instance of the class. Each such instance is used to represent a Java class. newInstance() An object of this class can be created by the method of this instance  . The actual situation may be more complicated. For example, Java byte codes may be dynamically generated by tools or downloaded through the network.

Take JDK 8 as an example: each class loader has its own responsibility, and there is a hierarchical relationship (for example, when an application loader loads a class, it will ask whether its superior extended class loader has been loaded, if not loaded, The extension class loader will be entrusted to continue to start the class loader to ask whether the class has been loaded), if the upper level has not loaded it, it will be loaded.

JDK 8 class loader
English name Chinese name Which classes to load Description
Bootstrap ClassLoader Start the class loader JAVA_HOME/jre/lib No direct access, written in C++, no direct JAVA
Extension ClassLoader Extended class loader JAVA_HOME/jre/lib/ext The upper level is Bootstrap, which is displayed as null
Application ClassLoader Application class loader classpath The upper level is Extension
Custom class loading Custom class loading customize The upper level is Application

1.1 The class loader loads Class roughly through the following 8 steps

  1. Check whether this Class has been loaded, that is, whether there is this Class in the buffer, if there is, go directly to step 8, otherwise go to step [2].
  2. If there is no parent class loader, then either the parent is the startup class loader or the startup class loader itself, then skip to step [4], if the parent class loader exists, go to step [3].
  3. Request to use the parent class loader to load the target class. If the loading is successful, skip to step [8], otherwise proceed to step [5].
  4. Request to use the startup class loader to load the target class. If the loading is successful, skip to step [8], otherwise skip to step [7].
  5. The current class loader tries to find the Class file, if it finds it, execute step [6], if it cannot find it, execute step [7].
  6. Load Class from the file and skip to step [8] after success.
  7. Throw a ClassNotFountException exception.
  8. Return the corresponding java.lang.Class object.

1.2 The class loading mechanism of JVM mainly has the following 3 kinds

  • Caching mechanism:
    • The caching mechanism will ensure that all loaded classes will be cached. When a class needs to be used in the program, the class loader first searches for the class in the cache area, and only when the Class object does not exist in the cache area, the system will The binary data corresponding to the class will be read, converted into a Class object, and stored in the buffer. This is why after modifying the Class, the JVM must be restarted so that the modifications made by the program will take effect.
  • Parent delegation:
    • The so-called parental delegation is to let the parent class loader try to load the Class first, and only try to load the class from its own class path when the parent class loader cannot load the class. In layman's terms, when a particular class loader receives a request to load a class, it first delegates the loading task to the parent loader, and then recursively. If the parent loader can complete the class loading task, it returns successfully; only the parent When the loader cannot complete the loading task, it loads itself.
  • Fully responsible for:
    • The so-called overall responsibility means that when a class loader is responsible for loading a certain Class, other classes that the Class depends on and references will also be loaded by the class loader, unless another class loader is used to load it.

1.3 Here is an explanation of the parent delegation mechanism

Parent delegation mechanism, its working principle:

If a class loader receives a class loading request, it does not load it first, but delegates the request to the loader of the parent class to execute. If the parent class loader still has its parent class loader, it will go further Delegating upwards, recursively, and the request will eventually reach the top-level startup class loader. If the parent class loader can complete the class loading task, it will return successfully. If the parent class loader cannot complete the loading task, the child loader will try itself To load, this is the parental delegation model, that is, every son is very lazy, and every time he has a job, he will leave it to his father to do it. Until his father says that I can't do it, the son will find a way to complete it. As shown below:

Schematic diagram of parent delegation

Advantages of the parent delegation mechanism: avoid repeated loading + avoid core class tampering.

The advantage of adopting the parental delegation mode is that the Java class has a priority hierarchical relationship along with its class loader. Through this hierarchical level, repeated loading of the class can be avoided. When the father has already loaded the class , There is no need to load the child ClassLoader again. Secondly, considering safety factors, the types defined in the java core api will not be replaced at will. Suppose a class named java.lang.Integer is passed through the network, and passed to the startup class loader through the parent delegation mode, and the startup class loader A class with this name is found in the core Java API, and it is found that the class has been loaded. The java.lang.Integer passed over from the network will not be reloaded, but the loaded Integer.class will be returned directly, which can prevent the core API The library was tampered with at will.


Two, start the class loader

Loading classes with Bootstrap class loader: code example

class F {
    static {
        System.out.println("bootstrap F init");
    }
}

Call F type execution 

// 用 Bootstrap 类加载器加载类
public class T01_ClassLoader_Bootstrap {
    public static void main(String[] args) throws ClassNotFoundException {
        // forName 可以完成类的加载,也可以类的链接、初始化操作
        Class<?> aClass = Class.forName("com.jvm.t10_class_loader.F");
        System.out.println(aClass.getClassLoader());
    }
}

 Output result: null, indicating that class F is loaded by the startup class loader

/Users/Li/IdeaProjects/JvmLearn> java -Xbootclasspath/a:.com.jvm.t10_class_loader.T01_ClassLoader_Bootstrap
bootstrap F init
null
  • -Xbootclasspath means to set bootclasspath
  • Where /a:. means append the current directory to the boot classpath
  • You can use this method to replace the core class (jvm development is often done, ordinary development does not need)
    • java -Xbootclasspath:<new bootclasspath>
    • java -Xbootclasspath/a:<append path>
    • java -Xbootclasspath/p:<append path>

Three, extended class loader

Test 1: When we write an ordinary class by ourselves, it is loaded by the application loading class

The test code example is as follows:

public class T02_G {
    static {
        // 1、打包:jar -cvf my.jar com\jvm\t10_class_loader\T02_G.class
        // 2、放入:jdk\jre\lib\ext\  下
        //System.out.println("ext G init");
        System.out.println("classpath G init");
    }
}
/**
 * 演示 扩展类加载器
 * 在 D:\Program Files\Java\jdk1.8.0_91 下有一个 my.jar
 * 里面也有一个 G 的类,观察到底是哪个类被加载了
 */
// 用扩展类加载器 Extension ClassLoader加载类
public class T02_ClassLoader_Extension {
    public static void main(String[] args) throws ClassNotFoundException {
        // forName 可以完成类的加载,也可以类的链接、初始化操作
        Class<?> aClass = Class.forName("com.jvm.t10_class_loader.T02_G");
        System.out.println(aClass.getClassLoader());
    }
}

Running result: when writing a common class by yourself, it is loaded by the application loading class


Test 2: If the class with the same name is also in the extended class loader path, it will be loaded by the extended class loader

Therefore, we modify the T02_G generation as follows:

public class T02_G {
    static {
        // 1、打包:jar -cvf my.jar com\jvm\t10_class_loader\T02_G.class
        // 2、放入:jdk\jre\lib\ext\  下
        System.out.println("ext G init");
        //System.out.println("classpath G init");
    }
}

T02_G compile and package: jar -cvf my.jar com\jvm\t10_class_loader\T02_G.class, put it under jdk\jre\lib\ext\; run T02_ClassLoader_Extension again 

Test result: If the class with the same name is also in the extended class loader path, it will be loaded by the extended class loader, that is , the  T02_G class is loaded by the extended class loader

Explanation of results:

Parent delegation mode, when we apply the application class loader to load the class, we have to ask whether its superior class loader has been loaded, the superior found the extended class loader, and the extended class loader found the G class of the same name. After the file is loaded, the result is that the application class loader has no chance to load it. The highest priority is the startup class loader, followed by the extended class loader, and the third is the application class loader.


Four, Parental Delegation-Source Code Analysis 1

The so-called parent delegation: refers to the rules for finding classes when the loadClass method of the class loader is called.

Note: For the parents here, it seems more appropriate to translate as superior, because they have no inheritance relationship. The industry generally calls the parental delegation model

Class loading ClassLoader is in  the ClassLoader.java file under the package java.lang package  . The core code is 395 lines . details as follows:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            // 1、检查类是否已经加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        // 2、有上级的话,委派上级 loadClass
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果没有上级了(ExtClassLoader), 则委派 BootstrapClassLoader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    // 4、每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    // 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;
        }
    }

Five, Parental Delegation-Source Code Analysis 2

The parental delegation mode avoids repeated loading of classes (the way the JVM distinguishes different classes is not only based on the class name, the same class file is loaded by different class loaders to produce two different classes).

public class T03_H {
    static {
        System.out.println("classpath T03_H init");
    }
}
// 类加载器 —— 单步调试查看过程
public class T03_ClassLoader_StepDebug {
    public static void main(String[] args) throws ClassNotFoundException {
        // forName 可以完成类的加载,也可以类的链接、初始化操作
        Class<?> aClass = Class.forName("com.jvm.t10_class_loader.T03_H");
        System.out.println(aClass.getClassLoader());
    }
}

 Single step debugging is as follows:

Six, thread context class loader

The thread context class loader is a special loader. When we use JDBC, we need to load the Driver driver, I don’t know if you noticed it, don’t write

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

It is also possible to make com.mysql.jdbc.Driver load correctly, do you know how to do it? Let's trace the source code:

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

Regardless of other things, take a look at the DriverManager class loader:

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

Print null, which means that its class loader is Bootstrap ClassLoader, and it will search for classes under JAVA_HOME/jre/lib, but obviously there is no mysql-connect-java-5.1.47.jar package under JAVA_HOME/jre/lib/, so the problem comes Now, how can com.mysql.jdbc.Driver be loaded correctly in the static code block of DriverManager?

Continue to look at the loadInitialDrivers() method: JDK needs to break the parental delegation model in some cases

    private static void loadInitialDriver() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                @Override
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }

        // 1) 使用ServiceLoader 机制加载驱动,即 SPI
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            @Override
            public Void run() {
                ServiceLoader<Driver> loaderDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loaderDrivers.iterator();
                try {
                    while (driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch (Throwable t) {
                    // Do nothing
                }
                return null;
            }
        });

        System.out.println("DriverManager.initialize: jdbc.drivers = " + drivers);

        // 2) 使用 jdbc.drivers 定义的驱动名加载驱动
        if (drivers == null || drivers.equals("")) {
            return;
        }

        String[] driversList = drivers.split(":");
        System.out.println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                System.out.println("DriverManager.Initialize: loading " + aDriver);
                // 这里的 ClassLoader.getSystemLoader() 就是应用程序类加载器
                // getSystemClassLoader 其实就是应用程序类加载器,打破了双亲委派模式。在某些情况下需要打破双亲委派模式
                Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                System.out.println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

Look at the following, it is the famous Service Provider Interface (SPI), mainly for decoupling

The convention is as follows, in the META-INF/services package of the jar package, the file is named with the fully qualified name of the interface, and the content of the file is the name of the implementation class. That is to find the file according to the interface, the content of the file is the class name of the class to be loaded

So you can use

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

To get the implementation class, it reflects the idea of ​​[interface-oriented programming + decoupling], which is used in the following frameworks:

  • JDBC
  • Servlet initializer
  • Spring container
  • Dubbo (expanded SPI)

Then look at the ServiceLoader.load method:

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

The thread context class loader is the class loader used by the current thread. The default is the application class loader. Class.forName calls the thread context class loader to complete the class loading. The specific code is inside java.util.ServiceLoader In the class LazyIterator:

        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
        }

Seven, custom class loader

Ask yourself, when do you need a custom class loader

  1. I want to load class files that are not in the classpath arbitrary path
  2. All are implemented through the use of interfaces, and when you want to decouple, they are often used in frame design
  3. These hopes are isolated, and classes of the same name in different applications can be loaded without conflict, which is common in tomcat containers

step:

  1. Inherit the ClassLoader parent class
  2. To comply with the parental delegation mechanism, rewrite the findClass() method
    1. Note: not overriding 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

Example:

Prepare two class files and put them into D:\myclasspath. It implements the java.util.Map interface. You can decompile it first:

Custom class loader implementation code: need to inherit the ClassLoader class

// 类加载器 —— 自定义类加载器
public class T05_ClassLoader_MyClassLoader extends ClassLoader {

    public static void main(String[] args) throws Exception {
        T05_ClassLoader_MyClassLoader classLoader = new T05_ClassLoader_MyClassLoader();
        Class<?> c1 = classLoader.loadClass("T05_ClassLoader_DefineV1");
        Class<?> c2 = classLoader.loadClass("T05_ClassLoader_DefineV1");
        System.out.println(c1 == c2); // true, 类文件只会加载一次,第一次加载放在自定义类加载器缓存中,第二次加载时发现缓存中已经有了

        // 如果使用不同的类加载器;确认唯一类,要包名、类名相同,同时类加载器也得相同,它会加载两次
        T05_ClassLoader_MyClassLoader classLoader2 = new T05_ClassLoader_MyClassLoader();
        Class<?> c3 = classLoader2.loadClass("T05_ClassLoader_DefineV1");
        System.out.println(c1 == c3); // false

        c1.newInstance();
    }


    @Override // name 就是类名称
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = "/Users/li/class_dir/" + 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);
        }
    }

}

 


At the end of the article, I recommend some popular technical blog links :

  1. Hadoop related technical blog links
  2. Spark core technology link
  3. JAVA related deep technical blog link
  4. Super dry goods-Flink mind map, it took about 3 weeks to compile and proofread
  5. Deepen the core principles of JAVA JVM to solve various online faults [with case]
  6. Please talk about your understanding of volatile? --A recent "hardcore contest" between Xiao Lizi and the interviewer
  7. Talk about RPC communication, an interview question that is often asked. Source code + notes, package understanding

 


Welcome to scan the QR code below or search the official account "10 o'clock advanced studies", we will push more and timely information to you, welcome to communicate!

                                           

       

Guess you like

Origin blog.csdn.net/weixin_32265569/article/details/108092624