JVM virtual machine detailed class loading subsystem

Detailed explanation of JVM virtual machine (class II) loading subsystem

1. Overview of memory structure

💡 Note: only the HotSpot virtual machine is available in the method area, but neither J9 nor JRockit

2. Class loader subsystem

  • Functions of class loader subsystem:
    • The class loader subsystem is responsible for loading class files from the file system or network. Class files have a specific file ID at the beginning of the file.
    • ClassLoader is only responsible for loading the class file. Whether it can run or not is decided by the Execution Engine.
    • The loaded Class information is stored in a memory space called the method area. In addition to the Class information, the method area also stores the runtime constant pool information, which may also include string literal and numeric constants (this part of the constant information is the memory mapping of the constant pool part in the Class file)

3. ClassLoader role

  • As like as two peas, the class file (Car.class file in the following figure) is on the local hard disk, which can be understood as a template for designers to draw on the paper. In the end, the template is loaded into JVM when executing, and n identical instances are generated according to the file instance.
  • The class file is loaded into the JVM, called the DNA metadata template (Car Class in memory in the figure below), and placed in the method area.
  • Yes Class file – > JVM – > eventually becomes a metadata template. This process requires a transport tool (Class Loader) to act as a courier.

4. Class loading process

For example, the following simple code

/**
 * Class loading subsystem
 * @author peppa
 * @create 2022-02-08 16:51:45
 */
public class HelloLoader {
    public static void main(String[] args) {
        System.out.println("I've been loaded...");
    }
}
  • How is its loading process?
    • To execute the main() method (static method), you need to first load the HelloLoader class where the main method is located
    • If the loading is successful, perform operations such as linking and initialization. After completion, call the static method main in the HelloLoader class.
    • If the loading fails, an exception is thrown

The complete flow chart is shown below

5. Loading phase

  • load:

    1. Get the binary byte stream defining this class through the fully qualified name of a class
    2. Convert the static storage structure represented by this byte stream into the runtime data structure of the method area
    3. Generate a Java. Net file representing this class in memory Lang. class object, as the access entry of various data of this class in the method area
  • How to load class files

    1. Load directly from local system
    2. Get through the network, typical scenario: Web Applet
    3. Read from the zip package and become the basis of jar and war formats in the future
    4. Dynamic agent technology is the most widely used in runtime computing generation
    5. Generated by other files, typical scenario: JSP Application extracted from proprietary database class file, relatively rare
    6. Obtained from encrypted files, typical protection measures against decompilation of Class files

6. Link phase

Links are divided into three sub stages: validation - > preparation - > parsing

  • Verify
    1. The purpose is to ensure that the information contained in the byte stream of the Class file meets the requirements of the current virtual machine, ensure the correctness of the loaded Class, and will not endanger the safety of the virtual machine
    2. It mainly includes four kinds of verification, file format verification, metadata verification, bytecode verification and symbol reference verification.
    • give an example

      Use BinaryViewer software to view bytecode files with CAFE BABE at the beginning. If there is an illegal bytecode file, it will fail the verification.

      Tool: Binary Viewer view, tool download address: Binary Viewer Page (proxoft.com)

If there is an illegal bytecode file, the verification will fail. At the same time, we can view our Class file by installing the plug-in of IDEA

- install*Binary Viewer*
    
    At the same time, we can install IDEA Plug-in to view our Class file

  • Configure plug-in parameters

    Click Help, click Edit Custom Vm Options... And restart the IDEA after adding the following configurations.

    ```java
    -Duser.language=en
    -Duser.region=CN
    ```
    

    After installation, after compiling a class file, click view to display the plug-in we installed to view the bytecode method

  • Prepare

    1. Allocate memory for class variables (static variables) and set the default initial value of such variables, i.e. zero value
    2. static modified with final is not included here, because final will allocate the default value when compiling and will be explicitly initialized in the preparation stage
    3. Note: initialization will not be allocated for instance variables here. Class variables will be allocated in the method area, and instance variables will be allocated to the Java heap together with the object
    • give an example

    Code: variable a will be assigned an initial value in the preparation phase, but not 1, but 0, and will be assigned a value of 1 in the initialization phase

    /**
     * @author peppa
     * @create 2022-02-08 17:36:28
     */
    public class HelloApp {
        private static int a = 1;  // The preparation stage is 0, and it is 1 in the next stage, that is, during initialization
        public static void main(String[] args) {
            System.out.println(a);
        }
    }
    
  • Resolve

    1. The process of converting a symbolic reference in a constant pool to a direct reference
    2. In fact, the parsing operation is often accompanied by the JVM after initialization
    3. A symbol reference is a set of symbols that describe the referenced target. The literal form of symbol reference is clearly defined in the class file format of java virtual machine specification. A direct reference is a pointer directly to the target, a relative offset, or a handle indirectly located to the target
    4. Parsing actions are mainly aimed at classes or interfaces, fields, class methods, interface methods, method types, etc. Corresponding to CONSTANT Class info, CONSTANT Fieldref info, CONSTANT Methodref info, etc. in constant pool
    • Symbol reference
      • After decompiling the class file, you can view the symbol reference. The symbol reference is shown # below

7. Initialization phase

  • Class initialization timing

    1. Create an instance of a class
    2. Access or assign a value to a static variable of a class or interface
    3. Calling a static method of a class
    4. Reflection (for example: Class.forName("com.atguigu.Test"))
    5. Initializes a subclass of a class
    6. The class marked as the startup class when the Java virtual machine starts
    7. JDK7 started to provide dynamic language support: Java lang.invoke. Parsing result of methodhandle instance REF_getStatic,REF putStatic,REF_ If the class corresponding to the invokestatic handle is not initialized, it will be initialized

    In addition to the above seven cases, other ways of using Java classes are regarded as passive use of classes, which will not lead to class initialization, that is, the initialization phase will not be executed (clinit() method and init() method will not be called)

  • Constructor method < clinit > () method

    1. The initialization phase is the process of executing the class constructor method < clinit > ()
    2. This method does not need to be defined. It is a combination of the assignment actions of all class variables in the class automatically collected by the javac compiler and the statements in the static code block. In other words, when we include static variables in our code, there will be clinit method
    3. The instructions in the < clinit > () method are executed in the order in which the statements appear in the source file
    4. < clinit > () is different from the constructor of a class. (Association: the constructor is < init > () from the perspective of virtual machine)
    5. If the class has a parent class, the JVM will ensure that the < clinit > () of the parent class has been executed before the < clinit > () of the child class is executed
    6. The virtual machine must ensure that the < clinit > () method of a class is locked synchronously under multithreading
  • Case 1: static variable

    Looking at the bytecode of the following code, you can find that there is a < clinit > () method.

    package com.peppa;
    
    /**
     * @author peppa
     * @create 2022-02-09 20:38:46
     */
    public class ClassInitTest {
        private static int age = 1;
        static {
            age = 2;
            number = 20;
            System.out.println(age);
            // System.out.println(number);  // Error: Illegal forward reference
        }
        /**
         * 1,linking Prepare: number = 0 -- > initial: 20 -- > 10
         * 2,Here, because the static code block appears in front of the declaration variable statement, the number variable previously prepared with 0 will be changed
         * First, it is initialized to 20, and then it is initialized to 10 (this is also a common question in the interview)
         */
        private static int number = 10;
    
        public static void main(String[] args) {
            System.out.println(ClassInitTest.age); // 2
            System.out.println(ClassInitTest.number); // 10
        }
    }
    

- <clint Bytecode>: When our code contains static Variable, there will be clinit method

```java
0: iconst_1
1: putstatic     #3                   // Field age:I
4: iconst_2
5: putstatic     #3                   // Field age:I
8: bipush        20                   // Xianfu 20                    
10: putstatic     #5                  // Field number:I
13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
16: getstatic     #3                  // Field age:I
19: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
22: bipush        10                  // Another 10       
24: putstatic     #5                  // Field number:I
27: return
```
  • Case 2: no static variable
package com.peppa;

/**
 * @author peppa
 * @create 2022-02-09 20:38:46
 */
public class NonStaticClassInitTest {
    private int age = 1;
    private int number = 10;

    public static void main(String[] args) {
        System.out.println("this is NonStaticClassInitTest.class");
    }
}

  • Case 3: about the variable assignment process when the parent class is involved

    • If the class has a parent class, the JVM will ensure that the < clinit > () of the parent class has been executed before the < clinit > () of the child class is executed
    package com.peppa;
    
    /**
     * @author peppa
     * @create 2022-02-09 22:32:26
     */
    public class SubClassInitTest {
        static class Animal {
            public static int A = 1;
            static {
                A = 2;
            }
        }
    
        static class Dog extends Animal {
            public static int B = A;
        }
    
        public static void main(String[] args) {
            System.out.println(Dog.B);  // 2
        }
    }
    

    According to the above code, the loading process is as follows:

    • First, the SubClassInitTest class needs to be loaded to execute the main() method
    • Get Dog B static variables, and the Dog class needs to be loaded
    • The parent class of dog class is Animal class, so you need to load Animal class first and then Dog class
  • Case 4: the virtual machine must ensure that the < clinit > () method of a class is locked synchronously under multithreading

    package com.peppa;
    
    public class DeadThreadTest {
        public static void main(String[] args) {
            Runnable r = () -> {
                System.out.println(Thread.currentThread().getName() + "start");
                DeadThread dead = new DeadThread();
                System.out.println(Thread.currentThread().getName() + "end");
            };
    
            Thread t1 = new Thread(r,"Thread 1");
            Thread t2 = new Thread(r,"Thread 2");
    
            t1.start();
            t2.start();
        }
    }
    
    class DeadThread{
        static{
            if(true){
                System.out.println(Thread.currentThread().getName() + "Initialize the current class");
                while(true){
    
                }
            }
        }
    }
    
    // Execution result:
    	Thread 2 start
    	Thread 1 started
    	Thread 2 initializes the current class
    
    	/**Then the program stuck**/
    

    If the program is stuck, analyze the cause:

    • Two threads load the DeadThread class at the same time, and there is a execution loop in the static code block in the DeadThread class
    • The thread that first loads the DeadThread class grabs the synchronization lock, and then executes an endless loop in the static code block of the class, while another thread is waiting for the release of the synchronization lock
    • Therefore, no matter which thread executes the loading of DeadThread class first, the other class will not continue to execute. (a class can only be loaded once)

Keywords: Java Back-end

Added by jmarcv on Tue, 01 Mar 2022 08:35:56 +0200