Class loading mechanism and class loader---Java virtual machine


Class loading mechanism and class loader—Java virtual machine

When a java class file is compiled, it becomes a bytecode file of class name .class. But the .class file at this time cannot be executed immediately, only when it is added to the method area in memory, and the After the data is verified, converted, analyzed, and initialized, it eventually becomes a Java type that can be used directly by the Java virtual machine. This process is called the class loading mechanism of the Java virtual machine . Let's understand it visually through two pictures, The position of the class loading process in the entire JVM architecture.
Insert picture description here

As you can see, there is a dedicated class loading subsystem in the JVM architecture to describe this process. There are two parts worthy of everyone’s understanding

  1. How is a .class file loaded: this is the class loading mechanism
  2. What is responsible for loading the class file in: this is the class loader

Let's take a closer look at the class loading subsystem from the following two aspects.

1. Class Loading Mechanism:

In the Java language, the loading, connection and initialization of types are completed during the running process of the program. Although this will cause a little extra overhead in the Java language, it provides extremely high scalability and flexibility for Java references. , Java's inherently dynamically expandable language features are realized by relying on dynamic loading and dynamic connection during runtime.

A type starts from being loaded into the virtual machine's memory, until it is unloaded from the memory, the entire life cycle will go through 7 small stages:

  1. Loading
  2. Verification
  3. Preparation (Preparation)
  4. Resolution
  5. Initialization (Initialization)
  6. Using (Using)
  7. Unloading

The three stages of verification, preparation, and analysis are collectively called Linking . The order of the five processes of loading, verification, preparation, initialization and unloading is determined. The following is a schematic diagram of the life cycle of the class:

Insert picture description here

1.1 Loading

Loading is just a process of ClassLoading. Don't confuse it. In the Loading phase, the java virtual machine needs to complete the following three things:

  1. Obtain the binary byte stream file that defines the class through the fully qualified name of a class : for example, the fully qualified name of the String class that everyone often uses is java.lang.String
  2. Convert the static storage structure represented by the byte stream into the runtime data structure in the Method Area: the class file is mainly loaded into the Method Area
  3. A java.lang.Class object representing this class is generated in memory as the access entry for various data of this class in the method area (reflection mechanism): various operations can be completed through the reflection mechanism

1.2 Verification

Verification is the first step in the connection phase.The purpose of this phase is to ensure that the information in the class byte stream meets all the requirements of the <Java Virtual Machine Specification> and that it will not cause harm to the JVM after it runs.

1.3 Preparation (Preparation)

The preparation phase is the stage of formally allocating memory and setting a zero value (except for final modified) variables defined in the ** class (static variables modified with static, also called class variables) . And instance variables (that is, useless static modified variables) **will be allocated in the java heap until a specific object is instantiated.
Insert picture description here

E.g:

	private static int a=1;//准备阶段分配内存,并且赋初始值0.初始化阶段初始化为1
    private int b=2;//new 一个新对象的时候才在堆中分配内存并赋值
    private final static int c=3;//准备阶段就会分配内存,并且赋初始值3

That is to say, the class variable will not be assigned according to the initial value in the code in the preparation phase, but will wait until the initialization phase. But if it is modified with final, it will be different. Because the variable modified with final will be compiled when javac Generate a ConstantValue property for it, so in the preparation phase, it will be assigned according to ConstantValue

1.4 Resolution

The parsing phase is the process in which the JVM replaces the symbolic references in the constant pool with direct references.

1.5 Initialization (Initialization)

The last action in the class loading process when the class is initialized. In the previous several processes, except for the partial participation of the user-defined loader during the loading phase, the rest is controlled by the JVM. Until In the initialization phase, the JVM will actually start to execute the Java program code written by the programmer in the class, and give the lead to the application.

In the preparation phase, class variables have been assigned zero values ​​(except for constants), but in the initialization phase, class variables and other resources will be initialized according to the programmer's coding. It can also be expressed from another more intuitive perspective: initialization The stage is the process of executing the <clinit>() method of the class constructor . <clinit>() is not written by the programmer, but Javac automatically collects all assignment actions and static statement blocks in the class (static()) The statement in <clinit>() is the order in which the statements appear in the source file.

For example, we write a piece of code and view the bytecode file through the Jclasslib plugin:

public class ClinitTest
{
    
    
    private int a=0;
    private static int b=1;
    static
    {
    
    
        b=2;
    }
}

The Jclasslib view results are as follows:

Insert picture description here

As you can see, the value of b is first assigned to 1 in order, and then the value of b is assigned to 2. There is no instance variable a.

Here are a few notes about the <clinit> method, but I will not demonstrate it.

  • Before the <clinit> method of this class is called, the JVM will ensure that the <clinit> of the parent class must have been executed, that is to say, the <clinit> of the Object class must be executed first.
  • The <clinit> method of a class is not necessary.If there is no copy of class variables and static code blocks in a class, the <clinit> method will not be generated for this class.
  • Static code blocks cannot be used in the interface, but variables can be declared, so the <clinit> method can also be generated, but the <clinit> method of the parent interface will not be executed before the <clinit> of the interface is executed. And the implementation class of the interface is being initialized The <clinit> method of the interface will not be executed during the process.
  • <clinit> will be correctly locked and synchronized, because a class can only be initialized once.

2. Class Loader (ClassLoader)

The code used to perform the action of "getting a binary byte stream describing the class through the fully qualified name of a class" in the loading phase is called a "class loader"

Schematic diagram of the class loader structure:

Insert picture description here

Many people see that the class loader is just a step in one stage of the class loading stage. It can't be a smaller function. Why should it be treated as a special part to book a special book?

In fact, it is not the case. It plays a role in the java program far beyond the class loading stage. For any class, the class loader that loads it and the class itself must determine its uniqueness in the JVM. Each class Loaders all have an independent class namespace. In a more popular language:

Condition to determine whether two classes are equal: (from the same class file && loaded by the same class loader)

2.1 Types of class loaders (JDK1.8)

From the perspective of the JVM, there are only two major types of class loaders:

  • Bootstrap ClassLoader: It is implemented in C++ language and is a part of JVM itself
  • Other class loaders: implemented by the Java language, independent of the virtual machine, inherited from the abstract class java.lang.ClassLoader

But from the perspective of java developers, the class loader is more detailed. Java has always maintained a three-tier class loader and parent-delegated class loading architecture.

For detailed classification, it can be divided into 4 types, sorted by priority as follows, the upper loader is the parent class loader of the following loader

  1. Bootstrap ClassLoader (Bootstrap ClassLoader)
  2. Extension ClassLoader
  3. Application ClassLoader (Application ClassLoader): also known as system class loader
  4. User Defined ClassLoader (UserDefined ClassLoader)

Insert picture description here

2.2 Bootstrap ClassLoader (Bootstrap ClassLoader)

The startup class loader is written by C++ code and is mainly used to load:

  1. Core classes in <JAVA_HOME>\lib such as rt.jar, classes in tools.jar
  2. Or the class specified by the JVM parameter VM Options:-Xbootclasspath parameter
ClassLoader classLoader = String.class.getClassLoader();//结果是null,但是不意味着不存在

The result is null, but it does not mean that it does not exist, because it is written in C++, so it cannot be represented at the Java level, and the result is null. In the JVM class loader, you can directly use null to refer to Bootstrap ClassLoader.

2.3 Extension ClassLoader (Extension ClassLoader)

This class loader is implemented in java code in the class sun.misc.Launcher$ExtClassLoader, and it is responsible for loading:

  1. <JAVA_HOME>\lib\ext directory
  2. Or all class libraries in the path specified by the java.ext.dirs system variable
        ClassLoader classLoader = SunEC.class.getClassLoader();
        //结果是sun.misc.Launcher$ExtClassLoader@72ea2f77
        System.out.println(classLoader);

2.4 Application ClassLoader (Application ClassLoader)

This class loader is implemented by sun.misc.Launcher$AppClassLoader, which is responsible for loading all class libraries on the user class path (ClassPath), that is, classes written by users and third-party class libraries.

        //springboot是第三方的类库
        ClassLoader classLoader = SpringBootCondition.class.getClassLoader();
        //结果是sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(classLoader);

        //ClassLoaderTest是我自己定义的一个类类
        ClassLoader classLoader1 = new ClassLoaderTest().getClass().getClassLoader();
        //结果是sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(classLoader1);

        //通过ClassLoader.getSystemClassLoader()可以获取ApplicationClassLoader
        ClassLoader ClassLoader3 = ClassLoader.getSystemClassLoader();
        //结果是sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(ClassLoader3);

And because it is the return value of the static method getSystemClassLoader() in ClassLoader, it is also called "system class loader".

2.5 User Defined ClassLoader (UserDefined ClassLoader)

By default, java provides 3 types of loaders, but for some special situations, programmers can customize their own class loaders, and only need to implement the ClassLoader interface.

3. The Parent Delegation Model and its destruction

3.1 Parental delegation model

The loading of classes in java uses dynamic loading, that is, only when the class is used will the corresponding bytecode file be loaded.
When a class loader receives a class loading request, it will not go immediately Load, but delegate this request to the parent class loader. Until it reaches the top Bootstrap loader.
If the parent class loader can complete the loading process of the class, then the loading of this class is handed over to the parent class to complete. Otherwise, it will Will be delegated to its subclass loader.

The following is the implementation of the parent delegation model:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
    
        //1 首先检查类是否被加载
        Class c = findLoadedClass(name);
        if (c == null) {
    
    
            try {
    
    
                if (parent != null) {
    
    
                    //2 没有则调用父类加载器的loadClass()方法;
                    c = parent.loadClass(name, false);
                } else {
    
    
                    //3 若父类加载器为空,则默认使用启动类加载器作为父加载器;
                    c = findBootstrapClass0(name);
                }
            } catch (ClassNotFoundException e) {
    
    
                //4 若父类加载失败,抛出ClassNotFoundException 异常后
                c = findClass(name);
            }
        }
        if (resolve) {
    
    
            //5 再调用自己的findClass() 方法。
            resolveClass(c);
        }
        return c;
    }

3.2 Benefits of the parent delegation model

  1. Avoid repeated loading of classes: Classes in Java and its class loader have a hierarchical relationship with priority. For example, the java.lang.Object class is the core class in Java, no matter which class loader To load this class, it will eventually be loaded by the top Bootstrap class, thus ensuring that the Objects in different environments are the same class (because the same class has two aspects).

  2. Protect the security of the program and prevent the core API from being arbitrarily tampered with: In programming, we often use third-party libraries or self-defined classes. If a fully qualified name appears in the program

    For classes of java.lang.Object, in the absence of a parent delegation model, multiple Object classes will appear, resulting in program confusion.

3.3 Destruction of the parental delegation model

The parent delegation model is not a model with mandatory constraints, but a class loader implementation recommended by Java designers to developers. In the Java world, most class loaders follow this model, but there are exceptions. Until the emergence of java modularization (JDK9), it had a total of 3 large-scale "destroyed" situations

  1. The first "broken" appeared before the parental delegation model (JDK1.2), because the parental delegation model was introduced in JDK1.2, but the ClassLoader already existed in JDK1.1. So before this model was Broken. After JDK1.2 introduced the parent delegation model, in the face of the existing user-defined class loader (mainly rewriting the loadClass method), some compromises had to be made. Because the parent delegation model is reflected in loadClass() In the method, and JDK1.1 users have rewritten the loadClass() method, which caused the model to be destroyed . So the designer defined a new protected method findClass() in the java.lang.ClassLoader class. Guide users Override this method when writing the class loading mechanism so that the model will not be destroyed.

    The following code is the loadClass method. Its logic is the parent delegation model. You can see the second if(c==null) code. When none of the three default class loaders of java complete the loading of the class Execute user-defined findClass(), which will not affect users to load classes according to their wishes, and will not destroy the parent delegation model.

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    
    
        synchronized (getClassLoadingLock(name)) {
    
    
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
    
    
                long t0 = System.nanoTime();
                try {
    
    
                    if (parent != null) {
    
    
                        c = parent.loadClass(name, false);
                    } else {
    
    
                        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();
                    c = findClass(name);//程序员重写findClass方法这样就不会破坏模型

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
    
    
                resolveClass(c);
            }
            return c;
        }
    }

2. Its second damage is caused by its own defects. The parent delegation model solves the problem of the consistency of the basic types when each class loader cooperates (the more basic types are determined by the upper class loader Load), but if the basic type needs to call the user's code, what should we do? In other words: programmers have implemented a very basic interface with their own code, which means that this very basic interface must be made by Bootstrap ClassLoader to load, but the class loader does not recognize the user's code ? What should I do?

To solve this dilemma, the Java design team had to introduce a less elegant design: Thread Context ClassLoader (Thread Context ClassLoader) . This class loader can be set by the setContextClassLoader() method of the java.lang.Thread class. If it is not set when the thread is created, it will inherit one from the parent thread; if it has not been set in the global scope of the application , Then this class loader is the application class loader by default. That is to say, the advanced class loader will use the thread file class loader to load the user's code, which actually breaks the parent delegation model.

Insert picture description here

3. The third "destroy" of the parental delegation model was caused by users' pursuit of program dynamics. IBM launched OSGi for this.

The core of OSGi's modular hot deployment lies in the realization of its custom class loader mechanism. All program modules (Bundles) have their own class loader. When a Bundle needs to be replaced, replace the Bundle together with the class loader to implement hot code replacement. In the illusion of OSGi, the class loader is no longer a tree structure in the parent delegation model, but has further developed into a more complex network structure. When a class loading request is received, OSGi will perform a class search in the following order:

  1. Assign the classes starting with java.* to the parent class loader to load.
  2. Otherwise, delegate the classes in the delegation list to the parent class loader to load.
  3. Otherwise, delegate the class in the Import list to the class loader of the Bundle of the Export class to load.
  4. Otherwise, find the ClassPath of the current Bundle and load it with your own class loader.
  5. Otherwise, find out whether the class is in its own Fragment Bundle, and if it is, then delegate it to the Fragment Bundle class loader to load it.
  6. Otherwise, find the Bundle in the Dynamic Import list and delegate to the class loader corresponding to the Bundle to load.
  7. Otherwise, the class loader fails.

The code word is not easy, can you give me a three-in-one for someone who writes well?

Guess you like

Origin blog.csdn.net/qq_44823898/article/details/111239669