When a program uses a class that has not already been loaded into memory, the JVM loads the class through three steps: load, link, and initialize.

The Java Class file

A class file is a set of 8-bit byte based binary streams in which data items are arranged in sequence without any delimiters. So the entire class file stores almost all the data necessary for the program to run. When encountering data items that need to occupy more than 8 bits of byte space, the data items are divided into several 8-bit bytes and stored in the first place.

Share a JVM knowledge map :(you can follow the official account in the figure to obtain)

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); }} Copy the code1.23.4.. 56.7.8..
Copy the code

We know that Java code written can’t run directly; it needs to be a class file. This compiles. Need to use the JDK built-in Java command can be achieved.

For example, if we generate a bytecode file for a class, we can get a class file simply by using javac Sumdemo.java. Of course, in real projects, we usually do not use javac commands to compile Java code manually. Instead, we use IDE or Maven, Grande and other tools to help us compile Java code into class files more easily.

The compiled class file is not a text file, so there is no way to open it directly. For example, if we use notepad++ to open it, we can find it is a mess.

So if you want to read it, you can decompile it using the Java P command. This is a Java built-in decompiler tool, let’s see how to use. As shown below:

The following files are generated by decompilation using Javap -v -p. Note that you don’t need to follow.java at this point because you decompile the class file 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 \ jva-frame \ jva-chapter01-demo \ SRC \main\ Java \com\itbbfx>javap -v -p SumDemo warning: The binary file SumDemo contains com.itbbfx.SumDemo # description information 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"Description publicclass com.itbbfx.SumDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC.ACC_SUPER# constant poolConstant 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
  #26Public com.itbbfx.sumdemo (); descriptor: ()Vflags: 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: 0Publicstatic void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;) Vflags: 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"Copy the code1.23.4.. 56.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.. 3940.. 4142.43.44.45.46.47.48.49.. 5051.52.53.54.55.. 5657.. 5859.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76..
Copy the code

To analyze the decompilation result, you can see that a class file contains several sections. The first section is some description of the class. It records where the class file was stored, when it was modified, the MD5 value of the class file, and the Java class from which it was compiled. The second part is still some descriptive information. Major version: 52 indicates JDK 8. The third part is the constant pool. The fourth part is the field information. Finally, the fifth part is the method information. In fact, there are all sorts of instructions throughout the file that are hard to read directly.

Can refer to instruction table inside, this article addresses: docs.oracle.com/javase/spec… .

Class file loading

So how does a JVM load a class file? When a class is created or referenced, if the virtual machine finds that the class has not been loaded before, it loads the class file into memory through the ClassLoader, which does three main things.

First: read the binary stream of the class,

Second: convert the binary stream into a method area data structure and store the data in the method area.

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

Loading.class files:

  • Load directly from the local system
  • This parameter is obtained over the network. The typical scenario is a Web Applet
  • Reading from zip files became the basis for future JAR and WAR formats
  • Runtime computational generation, most commonly used: dynamic proxy technology
  • Generated by other files, such as JSP applications
  • Extract. Class files from a proprietary database, which is rare
  • Obtained from encrypted files, a typical protection against Class files being decompiled

link

Once the load is complete, it goes to the linking step, which can be subdivided into validation, preparation, and parsing.

validation

Verification is used to verify that class files comply with specifications and ensure the correctness of loaded classes without compromising vm security. This includes multiple levels of verification, mainly including four types of verification, file format verification, metadata verification, bytecode verification, symbol reference verification.

File format verification: For example, if the file starts with 0xCAFEBABE, this can be opened using a hexadecimal editor. For example, use Beyond Compare to open the sumDemo. class file. You can also use other hexadecimal editors to open the view.

CAFEBABE is followed by a number called modulus.

Validation of metadata: for example, to verify that the class has parent validation, that the class implements final because final classes cannot be inherited, or that a non-abstract class. Does it implement all the abstract methods? If not, the class is invalid.

Bytecode validation: Bytecode validation is very complex. Just because a class file can pass bytecode verification does not mean the class is free of problems. But if it doesn’t pass bytecode validation, there’s definitely something wrong. The verification of bytecode mainly includes the checking of running, whether the parameters of data type and opcode operation match (for example, if the stack space is only 2 bytes, but it needs to be larger than 2 bytes, the bytecode is considered to be problematic), whether the jump instruction points to a reasonable location, and so on.

Symbolic reference validation: So let’s not go into symbolic references here, you’ll see later. Symbolic reference validation also involves verifying that the description class in the constant pool exists, that the access method or field exists, and that it has sufficient permissions. If you’ve made sure your code is secure. At startup, add -xverify: None to disable validation and speed up class loading.

For example: help – > Edit Custom VM Options.

— Xverify: None — Xverify: None — Xverify: None

You can also set this option for your Eclipse or other IDE to speed up the STARTUP of IDEA.

Ok, you can find that the verification steps are very detailed, but it is recommended that you do not pay too much attention to the details of the initial learning, you can understand it as a check Java class is not normal steps. If the class file is found to have no problems after verification, it will enter the preparation stage.

To prepare

The preparation phase is the phase where memory is formally allocated and initial values are set for class variables (static variables). The memory used by these variables is allocated in the method area.

The preparation process allocates memory for static variables of the class and initializes them to the system’s initial values. Final static variables are assigned a user-defined value directly during preparation. For example, private final static int Value = 123456, which we define as such, is directly assigned to value at this stage.

For static variables at this stage, the value is still 0. For example: Private static int value =123456; private static int value =123456; private static int value =123456; Once you’re ready, you can start parsing.

parsing

Symbolic references are literal references independent of the internal data structure and memory distribution of the virtual machine. It is easy to understand that a lot of symbolic references are made through the constant pool in the Class Class file. But when the program is actually running, symbolic references are not enough.

The purpose of parsing is to convert symbolic references to direct references. A symbolic reference is a reference to an object whose actual address is unknown to the Java class at compile time. So there’s only one symbol that says who I want to quote now. For example, in our class file constant pool, we store symbolic references, including fully qualified names of kernel interfaces, method references, member variable references, and so on. To actually refer to a class, method, or variable, convert the symbol to an object pointer or address offset that can be found. The converted reference is a direct reference.

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 system.out.println () operation:

In more general terms, a symbolic reference is a mark that says who is being referred to, whereas a direct reference is actually referring to the object. Once parsing is complete, the initialization phase begins.

Initialize the

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 the previous steps are all right, the manifest classes can be loaded into the system without any problems. At this point, the class will start executing Java bytecode. (that is, in the inception phase, you actually start executing the Java program code defined in the class).

The important work of the initialization phase is to execute the class’s initialization method :() method. The method that the JVM executes first. The class constructor method is a combination of the compiler’s automatic collection of all static variables in a class, its assignments, and static statements. Also called class constructor methods.

The code in the method is executed in the same order as in the source file. So let’s run an example, let’s run it here.

static int a=1;
static {
    a = 3;
}
public static void main(String[] args){ System.out.println(a); } Duplicate code1.23.4.. 56.7.8..
Copy the code

The output is: 3.

So you can see that for the static variable that we chose, we have a static block here that assigns a to 1, and then assigns a to 3. The method of “will combine the two pieces of code into one method, so the value of a will be 3. Now let’s change the order again, and the output after success is 1.

Methods of subclasses are called before methods of subclasses are called.

The JVM guarantees thread-safety of the method.

In addition, during JVM initialization code execution, if a new object is instantiated, the method is called, the instance variable is initialized, and the corresponding constructor code is executed.

static {
    System.out.println("JVM Static Block");
}
{
    System.out.println("JVM Building Blocks");
}
public SumDemo(){
    System.out.println("JVM constructor");
}
public static void main(String[] args) {
    System.out.println("main");
    newSumDemo(); } Duplicate code1.23.4.. 56.7.8.9.10.11.12.13.14..
Copy the code

So inside this class you have static code blocks, you have constructors, you have constructors, and you have a main method. So what’s the order of the run? Let’s run it.

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

Let’s look at this example again.

static {
    System.out.println("JVM Static Block");
}
{
    System.out.println("JVM Building Blocks");
}
public SumDemo(){
    System.out.println("JVM constructor");
}
public static void main(String[] args) {
  
   new Sub();
}


public class Super {
    static {
        System.out.println("Super static block");
    }
    public Super(){
        System.out.println("Super constructor");
    }
    {
        System.out.println("Super block");
    }
}


public class Sub extends Super {
    static {
        System.out.println("Sub static block");
    }
    public Sub(){
        System.out.println("Sub construction method");
    }
    {
        System.out.println("Sub building block"); } Duplicate code1.23.4.. 56.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.
Copy the code

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, constructors, and constructors. Run and see what the console outputs.

As you can see, the first static block of code printed in the JVM does not execute the constructor and constructor code. This makes sense, because we didn’t go to new SumDemo. And then when the code executes to the main method, it goes to the new Sub object, and before new it’s going to execute the method here, so it’s going to execute the code in the Sub class.

Subclass static (Sub) : static (Sub) : static (Sub); static (Sub) : static (Sub); And then we call the constructor, and we know that the order of execution of the constructor comes before the constructor, and we do the Super constructor, and then we do the Super constructor, and then we do the Sub constructor and the Sub constructor.

Use and Uninstallation

Any type must go through the complete three class loading steps of loading, linking, and initializing before it can be used. Once a type has successfully gone through these three steps, it is “all but ready” for developers to use its static class member information (e.g., static fields, static methods) that developers can access and call within the program, or to create an object instance for it using the new keyword.

Classes loaded by the Java VIRTUAL machine’s built-in class loader are never unloaded during the lifetime 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 accessible. Classes loaded by user-defined class loaders can be unloaded.

Startup class loaders load types that cannot be unloaded during the entire run (JVM and JLS specifications).

Types loaded by system classloaders and standard extension classloaders are unlikely to be unloaded during runtime, because instances of system classloaders or standard extension classes can almost always be accessed directly or indirectly during runtime, and the possibility of reaching unreachable is minimal. (Of course, this is possible when the virtual machine is about to exit, since either ClassLoader or Class(java.lang.class) instances also exist in the heap and follow the same garbage collection rules).

Types loaded by a developer’s custom class loader instance can only be unloaded in a very simple context, and usually with the help of a forced invocation of the virtual machine’s garbage collection. It can be expected that in slightly more complex application scenarios (especially when users are developing custom classloader instances using caching strategies to improve system performance), loaded types are also less likely to be unloaded at runtime (at least for an indefinite period of time).

Ok, once the initialization is complete, you can use the class, but you can uninstall it when you don’t use it. This is easy to understand. The following figure is just a more general class loading process. In fact, this process is not always followed when a class is loaded.

For example, parsing may not be done before initialization, but may be done after initialization.