[Learn JVM from scratch | Part 4] Classification of class loaders and parent delegation mechanism

Foreword:

In Java programming, Class Loader (Class Loader) plays an important role. The class loader is responsible for loading Java bytecode and converting it into executable objects, allowing us to use various classes and resources in the application. The design and implementation of the Java class loader aims to support dynamic expansion and modular programming, providing the Java language with great flexibility and maintainability.

The classification of class loaders is a key part of understanding the Java class loading mechanism. Different types of class loaders are responsible for loading different types of classes, and they have different loading strategies and priorities. In this article, we will delve into the classification of class loaders and introduce in detail the characteristics and usage scenarios of each class loader.

Table of contents

Foreword:

Class loader: 

What is a class loader: 

Application scenarios of class loaders:

Class loader classification (JDK8 and previous versions): 

Parent delegation mechanism:

What is the Parental Delegation Mechanism:

Interpretation of the source code of the parent delegation mechanism:

The role of the parent delegation mechanism:

Breaking the parental delegation mechanism:

Summarize:


 

Class loader: 

What is a class loader: 

ClassLoader is a technology provided by the JAVA virtual machine to applications to implement class and interface bytecode data.

The class loader only participates in the part of the loading process where the bytecode is obtained and loaded into memory.

Application scenarios of class loaders:

  1. Dynamic loading: Class loader allows new classes and resources to be dynamically loaded at runtime. This is useful for applications that need to load different classes based on specific conditions or user needs. For example, class loaders are often used in plug-in systems and modular development to dynamically load and unload modules to achieve flexible expansion and function customization.

  2. Hot deployment: The class loader can implement hot deployment, that is, replacing and updating loaded classes while the application is running. This allows us to make updates and fixes to the code without stopping the application. Hot deployment is very useful during the development and debugging stages, improving development efficiency and debugging experience.

  3. Version isolation: By using different class loaders to load different versions of classes, we can achieve version isolation of classes. This is very important in application upgrades and dependency management, to avoid conflicts and compatibility issues between different versions of classes.

  4. Security Controls: Class loaders can apply security policies that restrict or control access to specific code and resources. Through a custom class loader, we can implement custom security policies and perform permission control and verification on loaded classes.

  5. Bytecode enhancements and dynamic proxies: Class loaders can be used to implement bytecode enhancements and dynamic proxies. Through a custom class loader, we can modify the bytecode during the class loading process and add additional logic or functionality. This has important applications for technologies such as AOP (aspect-oriented programming) and proxy patterns.

And the class loader'sparental delegation mechanism is also a must-ask question in the interview. As long as the interviewer asks about JVM-related content, then The parent delegation mechanism must be a must-ask. Therefore, we need to learn the class loader related content well.

After understanding what a class loader is and its application scenarios, let's formally learn about class loaders.


Class loader classification (JDK8 and previous versions): 

  1. Bootstrap ClassLoader: It is part of the JVM and is responsible for loading Java core class libraries, such as classes in the java.lang package. It is the top-level class loader, usuallyimplemented using C++, and cannot be obtained directly in Java code< /span>. (Common and important)

  2. Extension ClassLoader: It is responsible for loading Java extension libraries, usually located in the lib/ext directory of the JRE. It is written in Java and loaded by the startup class loader. (Common but not important)

  3. Application ClassLoader: Also known as the system class loader (System ClassLoader), it is responsible for loading the class library specified on the user class path (Classpath) . It is the most commonly used class loader by developers and is also the default class loader.

In addition to the above three common class loaders, you can also customize the class loader by inheriting the java.lang.ClassLoader class. Custom class loaders can flexibly load classes to meet various specific needs, such as downloading class files from the network, decrypting, etc.

To sum up, common class loaders can be divided into: startup class loaders, extension class loaders, and application class loaders. Developers can also customize the class loader as needed.

In actual JAVA code, we may encounter a situation where a JAR package exists in the loading scope of multiple class loaders at the same time. At this time, we need a parental delegation mechanism to solve this problem:

Parent delegation mechanism:

What is the Parental Delegation Mechanism:

Parent Delegation Model is a working method of Java class loader, which is used to ensure the safety of class loading and Consistency.

According to the parent delegation mechanism,When a class loader needs to load a class, it will first delegate to its parent class loader to try to load. If the parent class loader can successfully load the class, then the Class object of the class is returned. If the parent class loader cannot load the class, the child class loader will try to load it itself.

 

To put it simply: the core of the parent delegation mechanism is to solve the problem of who loads a class. The process is to look up whether it has been loaded and try to load it down.

Note: The relationship between a loader and its parent class loader cannot be considered to be inheritance, although there is a "parent". The essence is to create a ClassLoader inside the loader to store its parent class loader.​ 

It should be noted that if we try to obtain the parent object of the extended class loader, the result will be null. It’s not that its parent class is not the startup class loader, it’s just thatbecause the startup class loader is part of the JVM and is implemented in C++ and cannot be obtained

Interpretation of the source code of the parent delegation mechanism:

The entire parent delegation mechanism is performed in Classload, so we mainly look at this part of the source code:

 When trying to load a class, we will call the loadClass method. The first parameter of the method is the name of the loaded class, < /span>The second parameter is whether to parse the class

Enter the loadClass method

 In fact, this code is relatively easy to understand. The overall logic is:

  1. Use findLoadedClass to find out whether the target class is loaded
  2. If the target class has not been loaded (c==null), then try to find the parent class loader of the current loader. If there is a parent class loader (parent!=null), hand the current class to the parent class loader to execute loadClass. method. If there is no parent class loader, let the startup class loader (BootstrapClassLoad) find and load it.
  3. If the target class still cannot be loaded after reaching the top-level loader, then we will let the current loader load it (c=findClass(name)), record the time and other information, and then return 0;
  4. If the target class has been loaded, return 0 directly;

Finally, let’s take a look at the findClass source code of c=findClass(name):

The role of the parent delegation mechanism:

  1. Avoid duplicate loading: By using the parent delegation mechanism, each class loader will delegate to its parent class loader before trying to load a class. This can prevent the same class from being loaded by multiple different class loaders, ensure the consistency of the class, and avoid conflicts and memory waste caused by repeated loading.

  2. Security guarantee: The core class library (such as Java's core class library) is loaded by the startup class loader, and user-defined classes are loaded by the application class loader. . This can ensure the security of the core class library and prevent user-defined classes from tampering with the behavior of the core class library.

  3. Isolation of classes: Classes loaded by different class loaders are located in different namespaces and are isolated from each other. Even if the fully qualified names of two classes are the same, classes loaded by different class loaders are considered different classes in the JVM. This isolation can effectively avoid class conflicts, allowing each class loader to load and manage classes independently.

  4. Extensibility: By customizing the class loader, Java's class loading mechanism can be extended to achieve specific loading requirements. Developers can customize class loaders to implement functions such as hot deployment and dynamic loading. A custom class loader can inherit the characteristics of the parent class loader and extend it according to business needs.

In general, the parent delegation mechanism can ensure the consistency, security, and isolation of classes and avoid repeated loading. It also provides flexible extensibility so that the class loader can be customized according to specific needs.

Although the parental delegation mechanism provides good security for the loading of JAVA classes and Convenience. But sometimes we have to break the parental delegation mechanism, for example:

A Tomcat container can run multiple WEB applications, and if a class A with the same name appears in these two applications, then Tomcat must ensure that both classes A are loaded and are different classes. If the parental delegation mechanism is not broken, then after the Class A in WEB1 is recorded, the own Class A in WEB2 will not be loaded successfully. According to the parental delegation mechanism, Class A in WEB1 will be returned directly at this time. At this point we need to break the parental delegation mechanism.

Breaking the parental delegation mechanism:

1. Customize the class loader and rewrite Classload (Tomcat usage strategy):

Through the above separate reading of the source code, I believe everyone has understood the basic process of the parent delegation mechanism. If we want to break the parental delegation mechanism, just rewrite the Classload method. Specifically, rewrite the following code block:

Code example:

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查类是否已经被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                // 检查类是否在系统类加载器中已经加载
                c = findClass(name);
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 在这里实现自定义的类加载逻辑
        // 可以从其他位置加载类的字节码,并使用 defineClass() 方法定义类
    }
}

But it should be noted that in the logic of this code, although we do not load any parent class loader for the custom class, it will also have a default parent class loader < a i=1>Application class loader, but we did not use the parent class loader when we rewrote loadClass.

If we just want to customize a loader and load some classes independently. At this time, you should not break the parent delegation mechanism, but choose to rewrite it in FindClass

2. Thread context class loader (JDBC usage strategy):

JDBC will use a package calledDriveManager when trying to connect to the database to manage various database drivers and load related drivers:< /span>

String url = "jdbc:mysql://localhost:3306/your_database_name";
String username = "your_username";
String password = "your_password";
Connection connection = DriverManager.getConnection(url, username, password);

 DriveManager is located in rt.jar and is loaded by the startup class loader.

And this package needs to load various database driver classes. This third-party package must be loaded in the application loading class. Then a problem arises:

That is to say, after the startup class loader has loadedDriveManager, for the various database drivers it needs to load,< a i=3>The startup class loader cannot load, it can only be loaded by the application class loader. Doesn't this break the bottom-up delegation principle of the parent delegation mechanism?

Let’s analyze it step by step. First, let’s introduce the SPI mechanism:

        SPI (Service Provider Interface) is a service discovery mechanism provided by Java. It allows developers to define service interfaces and allows third-party vendors to extend the functionality of an application by providing implementations under the application's classpath.

The SPI mechanism works as follows: First, developers define a service interface and one or more classes that provide service implementations for the interface. Then, create a configuration file in the class path of the application. The name of the file must be "META-INF/services/fully qualified name of the interface", where the fully qualified name of the interface is used as the file name, and its content is The fully qualified name of the service interface implementation class.

When the application is initialized, the Java runtime will use Java's reflection mechanism to read and load the implementation class of the service interface from the configuration file on the classpath. In this way, the application can obtain an instance of the implementation class and use the functions it provides.

And ourDriveManager quickly finds the driver to be loaded by the JaR package through the SPI mechanism.

Based on the SPI mechanism, the overall process of loading DriveManager is:

After understanding the overall process of loading DriveManager based on the SPI mechanism, let’s think about it again:How does the SPI mechanism break the delegation from below under the parental delegation mechanism? Woolen cloth?

In the SPI mechanism, the thread context class loader (Thread Context Class Loader) is usually used to load specific implementation classes. Thread context class loader is a concept introduced in multi-threaded environment to specify the class loader for each thread. The thread context class loader is usually set via the Thread.currentThread().setContextClassLoader() method.

In the SPI mechanism,the thread context class loader can solve the problem of delegation from the bottom up under the parent delegation model. Specifically, when the code of the SPI implementation framework is located in a class library and the SPI implementation class customized by the application is located under the application's class path, the SPI implementation cannot be loaded directly by the application due to the limitations of the parent delegation model. kind. At this time, the SPI implementation class can be loaded by using the thread context class loader in the application, that is,setting the thread context class loader to the application's class loader. In this way, the SPI implementation framework can load the SPI implementation classes in the application through the thread context class loader, thereby breaking the limitations of the parent delegation model.

It should be noted that the SPI mechanism relies on the correct setting of the thread context class loader. Therefore, when using the SPI mechanism, you need to ensure that the thread context class loader is correctly set to ensure that the SPI implementation framework can correctly load the SPI implementation class in the application. .

To put it simply: SPI has a context class loader, which can save an application class loader in advance. Then when we use the startup class loader to load the DriveManager, and the DriveManager needs to load the database driver, the DriveManager will call the context class loader, causing the current loader to change from the startup class loader to the application class loader.

But in fact, there is still controversy about the way the context loader breaks the parental delegation mechanism.

  • Some people think that he did break the parental delegation mechanism: Because DriveManager is loaded by the startup class loader, but it needs to delegate the program class loader to record during the recording process, breaking the parental delegation Delegation of mechanisms is a top-down rule.
  • Some people think that he did not break the parental delegation mechanism: Because during the entire class loading process, DriveManager is in the java core package rt.jar, so it is loaded by the startup class loader; The database driver in the jar package is a third-party package and therefore is loaded from the application class loader. Whether it is loading the DriveManager class or the database driver class, the loadClass method is not overridden. As long as you use the native loadClass, you still follow the parental delegation mechanism

3. OSGI framework class loader:

Historically, the OSGI modular framework broke the parent delegation mechanism. It has delegated loading of class recorders between peers.

OSGi (Open Services Gateway) is a specification and framework for building modular, dynamic, and extensible Java applications.

Modularization refers to splitting an application into multiple independent modules (also called bundles), each containing its own code and resources. This modular design allows developers to more flexibly manage and maintain applications, improving reusability and maintainability.

In the earliest days, JAVA did not have the idea of ​​modularity. All jar packages were managed inrt.jar, while OSGi It provides a way to put jar packages with similar functions into one jar package for unified management.

In the OSGi framework, each module is called a bundle, and the bundle can contain its own classes and resources. OSGi uses its own class loader implementation, called BundleClassLoader.

BundleClassLoader is the core class loader in the OSGi framework, which breaks the parental delegation mechanism when loading classes. It first tries to load the class itself, and if it cannot find the required class, it delegates to the parent class loader. This mechanism is different from the standard parent delegation mechanism because the BundleClassLoader first attempts to load itself and does not necessarily follow the parent-first principle.

Summarize:

Class loaders and parent delegation mechanisms are key concepts in Java. Through class loaders, Java programs can dynamically load classes to achieve code flexibility and scalability. The parent delegation mechanism ensures the uniqueness and consistency of classes and avoids repeated loading and conflicts. Understanding class loaders and parent delegation is critical to resolving class path conflicts, achieving modular development, and ensuring security. They are of great significance for mastering Java's dynamic loading capabilities and modular development. With an in-depth understanding of its principles and mechanisms, you can better utilize the flexibility and scalability of Java and develop excellent applications. In some cases, it may be necessary to customize and extend the class loader behavior to further its usefulness. In short, the class loader and parent delegation mechanism are core components of the Java virtual machine and are crucial to understanding and mastering Java's dynamic loading capabilities and modular development.

If my content is helpful to you, pleaselike, comment, and collect it. Creating is not easy, and everyone’s support is what keeps me going!

 

 

Guess you like

Origin blog.csdn.net/fckbb/article/details/134842083