The most simple and easy-to-understand JAVA virtual machine class loading process is explained in detail. If you don't understand it after reading it, consider changing careers!

foreword

When a program uses a class, if the class has not been loaded into the memory, the JVM will load the class through the three steps of loading, linking, and initialization.

Java Class file

A class file is a set of binary streams based on 8-bit bytes, and each data item is arranged in the class file in order without any separators. Therefore, the content stored in the entire class file is almost all the necessary data when the program is running. When encountering a data item that needs to occupy more than 8 bytes of space, it will be divided into several 8-bit bytes for storage according to the high-order first.

Share a JVM knowledge map: (Follow me to share more dry goods ↓↓↓)
insert image description here

We first need to define a Java class:

public class SumDemo {
    public static void main(String[] args) {
        int a=1;
        int b=2;
        System.out.println(a+b);
    }
}
1.2.3.4.5.6.7.8.

We know that the Java code written cannot be run directly, it needs to be turned into a class file. This compiles. You need to use the built-in Java pair of commands in the JDK to achieve this.

For example, if we generate the bytecode file of a certain class, we only need to use javac SumDemo.java to get a class file. Of course, in actual projects, we generally do not use the javac command to compile manually, but use IDE or Maven, Grande and other tools to help us more conveniently compile Java code into class files.

The compiled class file is not a text file. It cannot be opened and read directly. For example, if we use notepad++ to open it, we can find that it is a piece of garbled code.

Then if you want to read it, you can use the java p command to decompile it. This is a Java built-in decompilation tool, let's see how to use it.

As shown in the figure below:

The following is the file generated by decompiling with javap -v -p. Note that you don't need to keep up with .java at this time, because the class file is decompiled, so you can see the contents of the class file. It can also be output to a .txt file using the command: javap -v -p xxx > xxx.txt .

E:\JavaSpace\Java-Prepare-Lesson\Java-High\JVM-Frame\JVM-Chapter01-Demo\src\main\java\com\itbbfx>javap -v -p SumDemo
警告: 二进制文件SumDemo包含com.itbbfx.SumDemo
#描述信息
Classfile /E:/JavaSpace/Java-Prepare-Lesson/Java-High/JVM-Frame/JVM-Chapter01-Demo/src/main/java/com/itbbfx/SumDemo.class
  Last modified 2021-5-16; size 409 bytes
  MD5 checksum b9b13ea5dba3f2b62f4764d30eafc7fc
  Compiled from "SumDemo.java"
#描述信息
public class com.itbbfx.SumDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
#常量池
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
   #4 = Class              #19            // com/itbbfx/SumDemo
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               SumDemo.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #21            // java/lang/System
  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #17 = Class              #24            // java/io/PrintStream
  #18 = NameAndType        #25:#26        // println:(I)V
  #19 = Utf8               com/itbbfx/SumDemo
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
#字段信息
  public com.itbbfx.SumDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
#方法信息
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: iload_1
         8: iload_2
         9: iadd
        10: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        13: return
      LineNumberTable:
        line 18: 0
        line 19: 2
        line 20: 4
        line 21: 13
}
SourceFile: "SumDemo.java"
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.

To analyze the results after decompilation, you can see that a class file contains several parts, the first part is some description information of the class. Records the location where the class file is stored, when it was modified, the MD5 value of the class file, and which Java class it was compiled from. The second part is some descriptive information. It mainly describes what version of JDK this class is compiled with, major version: 52 means JDK 8. The third part is the constant pool. The fourth part is the field information. Finally, the fifth part is the information of the method. In fact, there are various instructions in the whole file, and it is difficult for us to directly understand these instructions.

class file loading

So how does the JVM load the class file? When a class is created or referenced, if the virtual machine finds that the class has not been loaded before, it will load the class file into the memory through the class loader, that is, the ClassLoader. In the process of loading, it mainly does three things thing.

First: read the binary stream of the class,

Second: Convert the binary stream to the data structure of the method area, and store the data in the method area.

Finally: a java.lang.Class object will be generated in the Java heap.

The way to load the .class file:

  • Load directly from the local system
  • Obtained through the network, typical scenario: Web Applet
  • Reading from zip compressed files will become the basis for future jar and war formats
  • Run-time calculation generation, the most used is: dynamic proxy technology
  • Generated from other files, such as JSP applications
  • Extract .class files from proprietary databases, which is relatively rare
  • Obtained from encrypted files, typical protection measures against decompilation of Class files

Link

After the loading is completed, it will enter the linking step, which can be subdivided into verification, preparation and parsing.

verify

To verify and understand is to verify whether the class file conforms to the specification, to ensure the correctness of the loaded class, and not to endanger the security of the virtual machine itself. This includes multiple levels of verification, mainly including four types of verification, file format verification, metadata verification, bytecode verification, and symbol reference verification.

Verification of the file format: For example, whether the file starts with 0xCAFEBABE, you can use a hexadecimal editor to open and view it. For example, we use Beyond Compare to open the SumDemo.class file and take a look. You can also use other hex editors to open and view.

Here you can see that CAFEBABE is followed by a number, which is called the modulus.

Metadata verification: For example, verify whether this class has a parent class verification, whether this class implements final, because final classes cannot be inherited, and for example, a non-abstract class. Whether all abstract methods are implemented, if not, this class is also invalid.

Bytecode Verification: Bytecode verification is very complex. The fact that a class file can pass the bytecode verification does not mean that there is no problem with the class. But if it does not pass the verification of the bytecode, then there must be a problem. The verification of the bytecode is mainly determined by whether the parameters including the running check, data type and opcode operation match (for example: for example, the stack space is only 2 bytes, but in fact it needs to be larger than 2 bytes, at this time it is considered that this word section code is problematic) and whether the jump instruction points to a reasonable location, etc.

Symbolic reference verification: Then we will not discuss what symbolic reference is here, and everyone will know it later. Symbolic reference verification also includes verifying whether the description class in the constant pool exists, whether the access method or field exists, and has sufficient permissions. If you have confirmed in advance that your code is safe and correct. Then you can add such parameters at startup: -Xverify: none, to turn off the verification, is to speed up the loading of the class.

For example: IDEA's help -> Edit Custom VM Options.

Here you can view the startup parameters of IDEA, there is an item -Xverify: none in its startup parameters.

In this way, the verification will not be performed when starting IDEA, thereby speeding up the startup of IDEA. You can also set this option for your eclipse or other IDEs to speed up the startup of IDEA.

Well, you can find that the verification steps are very detailed, but it is recommended that you don't pay too much attention to the details when you are a beginner. You can understand it as a step to verify whether a Java class is normal. If it is verified and found that there is no problem with the class file, it will enter the preparation stage.

Prepare

The preparation phase is the phase of formally allocating memory for class variables (static variables) and setting initial values. The memory used by these variables will be allocated in the method area.

The role of this link is to allocate memory for the static variables of the class and initialize it to the initial value of the system. Then, for variables modified by final static, the variable will be assigned a value directly in the preparation process, which is a user-defined value. For example, private final static int value = 123456, we define it like this, and at this stage, 123456 will be directly assigned to the value.

But for static variables at this stage, its value is still 0, for example: private static int value = 123456, the value at this stage is still 0, not 123, because no Java method has been executed yet, assign the variable to 123456, yes will be executed during the initialization phase). After the preparation is complete, you can enter the analysis.

Parse

Symbolic references are literal references, which have nothing to do with the internal data structure and memory distribution of the virtual machine. It is relatively easy to understand that in the Class class file, a large number of symbolic references are made through the constant pool. But when the program is actually running, a symbolic reference is not enough.

The role of parsing is to convert symbolic references into direct references. The so-called symbolic reference means that at compile time, the Java class does not yet know the actual address of the referenced object. So just use a symbol to say who I want to refer to right now. For example, in the constant pool of our class file, all symbolic references are stored, including the fully qualified name of the kernel interface, the reference of the method, the reference of the member variable and so on. So if you want to really refer to these classes, methods or variables, you need to convert these symbols into object pointers or address offsets that can be found, and the converted references are direct references.

For example, when the following println() method is called, the system needs to know exactly where the method is located. Example: Output the bytecode corresponding to the operation System.out.println():

If it is more popular, symbolic reference means that a mark is made, which is who is to be referenced now, and the direct use is to really refer to the object. After the parsing is complete, it will enter the initialization phase.

initialization

The initialization phase, in short, assigns the correct initial values ​​to the static variables of the class. Class initialization is the final stage of class loading. If there are no problems with the previous steps, then the presentation class can be loaded into the system smoothly. At this point, the class starts executing Java bytecode. (That is: to the initialization phase, the actual execution of the Java program code defined in the class begins).

The important work of the initialization phase is to execute the initialization method of the class: () method. The method that the JVM will execute first. The method is that the compiler automatically collects all the static variables in the class, and its assignment actions and static statements are quickly merged, also called the class constructor method.

The code execution order in the method is the same as the order in the source file. Let's run an example to see, let's run it here.

static int a=1;
static {
    a = 3;
}
public static void main(String[] args) {
    System.out.println(a);
}
1.2.3.4.5.6.7.8.

The output result is: 3.

It can be seen that we have chosen a static variable, and there is another static code block here, first assigning a value of 1, and then assigning a value of 3. The method of will combine these two pieces of code into one method, so the final value of a is 3. Next, let's adjust the order again. After the execution is successful, the output result is 1.

The subclass's method is called before the superclass's method is called.

The JVM will guarantee the thread safety of the method.

In addition, when the JVM initialization code is executed, if a new object is instantiated, the method will be called, the instance variable will be initialized, and the code in the corresponding constructor will be executed. Let's run an example again.

static {
    System.out.println("JVM 静态块");
}
{
    System.out.println("JVM 构造块");
}
public SumDemo(){
    System.out.println("JVM 构造方法");
}
public static void main(String[] args) {
    System.out.println("main");
    new SumDemo();
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.

Then in this class there are static code blocks, construction blocks, construction methods, and a main method. So what is the order of execution? Run it and see.

You can see that the static code block is executed first, then the main method is executed, then the constructor is executed, and finally the constructor.

Let's look at this example again.

static {
    System.out.println("JVM 静态块");
}
{
    System.out.println("JVM 构造块");
}
public SumDemo(){
    System.out.println("JVM 构造方法");
}
public static void main(String[] args) {
  
   new Sub();
}


public class Super {
    static {
        System.out.println("Super 静态块");
    }
    public Super(){
        System.out.println("Super 构造方法");
    }
    {
        System.out.println("Super 构造块");
    }
}


public class Sub extends Super {
    static {
        System.out.println("Sub 静态块");
    }
    public Sub(){
        System.out.println("Sub 构造方法");
    }
    {
        System.out.println("Sub 构造块");
    }
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.

In this case, the main method instantiates a Sub object, and the Sub class inherits from Super. In this case, , SumDemo, Supper and Sub all have static code blocks, construction methods and construction blocks, run them and see what the console outputs.

It can be seen that the static code block in the JVM is printed first, and the code of the constructor and the constructor is not executed. This is easy to understand because we didn't go to new SumDemo. Then when the code executes to the main method, it goes to the new Sub object. Before new, it executes the method here, so it executes the code in the Sub class.

But before we said that before the method of the subclass is called, the method of the parent class will be called, so it will first call the static method of the Super class, and then call the static method of the Sub class, after the call is completed. We call the construction block again. We know that the order of execution of the construction block is before the construction method. The construction block of the Super class is executed first, then the construction method of the Super class is executed, and finally the construction block of the Sub class and the construction of the Sub class are executed. method.

use and uninstall

Before any type can be used, it must go through the complete 3 class loading steps of loading, linking and initializing. Once a type has successfully gone through these 3 steps, it is "all things ready, only the wind is left", waiting for the developer to use the static class member information that the developer can access and call it in the program (for example: static field , static methods), or create an object instance for it using the new keyword.

The classes loaded by the class loader that comes with the Java virtual machine will never be unloaded during the life cycle of the virtual machine. The Java virtual machine itself always references these class loaders, and these class loaders always reference the Class objects of the classes they load, so these Class objects are always reachable. Classes loaded by a user-defined class loader can be unloaded.

Types loaded by the startup class loader cannot be unloaded at runtime (JVM and JLS specifications).

Types loaded by the system class loader and the standard extension class loader are unlikely to be unloaded during runtime, because the system class loader instance or the instance of the standard extension class is basically always directly or indirectly accessible throughout the runtime. , which is extremely unlikely to be unreachable. (Of course, it can be done when the virtual machine is about to exit, because no matter whether the ClassLoader instance or the Class (java.lang.Class) instance also exists in the heap, it also follows the rules of garbage collection).

The types loaded by the developer-defined class loader instance can only be unloaded in a very simple context, and generally can be done by forcing the garbage collection function of the virtual machine to be called. It is conceivable that it is a little more complicated In many application scenarios (especially in many cases, users use caching strategies to improve system performance when developing custom class loader instances), the loaded types are almost unlikely to be unloaded during runtime (at least the time of unloading). is uncertain).

Well, after the initialization is complete, you can use this class, but you can unload it when you don't use it. This is easy to understand. The following figure is just a more conventional class loading process. In fact, when the class is loaded, it does not necessarily follow this process completely.


For example, parsing is not necessarily before initialization, and it may be done after initialization.

finally

insert image description here

I have compiled a document related to the java (jvm) virtual machine here , as well as: Spring series family buckets, Java systematic information: (including Java core knowledge points, interview topics and the latest 21 years of Internet real questions, e-books etc.) Friends in need can follow the official account [Program Yuan Xiaowan] to get it.

Guess you like

Origin blog.csdn.net/m0_48795607/article/details/120070921