JVM mechanism-the timing of class loading, the process of class loading, parent delegation and the destruction of parent delegation model

1 Overview

The JVM virtual machine loads the data describing the class from the Class file to the memory, and performs verification, conversion, analysis, and initialization of the data, and finally forms a Java type that can be directly used by the JVM. This is the JVM's class loading mechanism .

In the Java language, the process of class loading, connection and initialization are all completed during the running of the program .

2. Timing of class loading

From the moment the class is loaded into the memory until the memory is unloaded, the life cycle of the class includes 7 processes of loading , verification , preparation , analysis , initialization , use and unloading .

JVM mechanism-the timing of class loading, the process of class loading, parent delegation and the destruction of parent delegation model

 

  1. Under what circumstances need to start the first stage of the class loading process: loading ? There are no mandatory constraints in this Java virtual machine specification, and the specific implementation of the virtual machine can be freely controlled.
  2. However, for the initialization phase, the virtual machine specification strictly stipulates that there are only five cases where the class must be " initialized " immediately (loading, verification, and preparation are completed before this):

(1) When encountering the 4 bytecode instructions of new, getstatic, putstatic or invokestatic , if the class has not been initialized, it needs to trigger initialization first. The most common Java code scenario for generating these 4 instructions is: when using the new keyword to instantiate an object, when reading or setting a static variable of a class (modified by final, the result has been put into the constant pool at compile time Except for static constant fields) and when calling a static method of a class.

(2) When using the java.lang.reflect package method to make a reflective call to a class, if the class has not been initialized, its initialization needs to be triggered first.

(3) When a class is initialized, if it is found that the parent has not been initialized, you need to trigger initialization of the parent class.

(4) When the virtual machine starts, the user needs to specify a main class to be executed (the class containing the main() method), and the virtual machine initializes the main class first.

(5) When using the dynamic language support of JDK1.7, if the final analysis result of a java.lang.invoke.MethodHandle instance is the method handle of REF_getStatic, REF_putStatic, REF_invokeStatic , and the class corresponding to this method handle has not been initialized , You need to trigger its initialization first.

The behavior of the above five scenarios is called active reference to a class . In addition, all other ways of referencing classes will not trigger initialization, which is called passive referencing . Common examples of passive references include: (1) Referencing the static variables of the parent class through the subclass will not cause the subclass to initialize. System.out.println(SubClass.staticValue); (2) Referencing a class through an array definition will not trigger the initialization of this class. SuperClass[] arr = new SuperClass[10]; (3)  Static constants are stored in the constant pool of the calling class during the compilation phase, and in essence they are not directly referenced to the defined constants. System.out.println(ConstClass.finalStaticValue);

3. The process of class loading

The whole process of JVM class loading includes: loading, verification, preparation, analysis and initialization.

3.1 Loading

During the loading phase, the JVM needs to complete the following 3 things:

  1. Obtain the binary byte stream that defines this class through the fully qualified name of a class (such as java.lang.String) .
  2. The static storage structure represented by this byte stream is transformed into the runtime data structure of the method area .
  3. An object instance of java.lang.Class representing this class is generated in the memory as the entrance of various data access in this class in the method area .

3.2 Verification

Verification is the first step in the connection phase. The purpose of this phase is to ensure that the information contained in the byte stream of the Class file meets the requirements of the current virtual machine and does not endanger the security of the virtual machine itself . On the whole, the verification phase will generally complete the following four phases of verification actions: file format verification, metadata verification, bytecode verification, and symbol reference verification.

3.2.1 File format verification

The first stage is to verify whether the byte stream conforms to the specifications of the Class file format and can be processed by the current version of the virtual machine.

3.2.2 Metadata verification

The second stage is to perform semantic analysis on the information described by the bytecode to ensure that the information described meets the requirements of the Java language specification. The verification points that may be included in this stage are as follows:

  1. Does this class have a parent class (except for java.lang.Object, all classes should have a parent class).
  2. Whether the parent class of this class inherits a class that is not allowed to be inherited (a class that is final modified).
  3. If this class is not an abstract class, whether it has implemented all the methods required to be implemented in its parent class or interface. and many more

3.2.3 Bytecode verification

The third stage is the most complicated stage in the entire verification process. The main purpose is to determine that the program semantics are legal and logical through data flow and control flow analysis.

3.2.4 Symbol reference verification

The last stage of verification occurs when the virtual machine converts symbol references into direct references . This conversion action will occur in the third stage of the connection: the parsing phase . Symbol reference verification can be seen as a matching verification of information other than the class itself ( various symbol references in the constant pool  ) .

3.3 Preparation

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

  1. Memory allocation only includes class variables, not instance variables, which will be allocated in the heap along with the object when the object is instantiated.
  2. The initial value mentioned here is the zero value of the data type under "usual conditions". Suppose a class variable is defined as:
public static int value=123;

​ Then, the value of the variable value after the preparation phase is 0 instead of 123. Because this time has not yet started any java method, and the value assigned to 123 putstatic instruction after the program is compiled, stored in a class constructor method () in, so the value assigned to the operation of 123 will be the initialization phase will carried out.

  1. The "special case" is: when the field attribute of the class field is ConstantValue (such as a static constant), it will be initialized to the specified value in the preparation phase, so after marking as final, the value of value is initialized to 123 instead of 0 in the preparation phase.
public static final intvalue =123;

3.4 Analysis

Parsing stage is a virtual machine often amounts pool of symbolic references replaced by a direct reference to the process.

  1. Symbolic References: Symbolic references use a set of symbols to describe the referenced target. The symbol can be any form of literal, as long as it can be used to locate the target without ambiguity . Target reference and may not have been loaded into memory in.
  2. Direct References: A direct reference can be a pointer that directly points to the target , a relative offset, or a handle that can indirectly locate the target . If there is a direct reference, the referenced target must already exist in memory .

3.5 Initialization

In the initialization phase, the Java program code (or bytecode) defined in the class is actually executed.

  1. The initialization phase is the process of executing the class constructor <clinit>() method. The <clinit>() method is generated by the compiler automatically collecting the assignment actions of all class variables in the class and the statements in the static block (static{} block). The order in which the compiler collects is determined by the order in which the statements appear in the source file. In a static statement block, only variables defined before the static statement block can be accessed. The variables defined after it can be used in the previous static statement block. Assigned, but cannot be accessed.
    public classTest{
        static { 
            i =0;// 给变量赋值可以正常编译通过System.out.print(i);// 这句编译器会提示“非法向前引用”
        }
        static int i =1;
    }
  1. The <clinit>() method is different from the class constructor (or instance constructor <init>() method). It does not need to explicitly call the parent class constructor, and the virtual opportunity is guaranteed to be in the subclass <clinit>() Before the method is executed, the <clinit>() method of the parent class has been executed. Therefore, the class of the first <clinit>() method executed in the virtual machine must be java.lang.Object . Since the <clinit>() method of the parent class is executed first, it means that the static statement block defined in the parent class has priority over the variable assignment operation of the subclass. In the following code, the value of field B will be 2 instead of 1. .
    static class Parent {
        public static int A = 1;
        static {
            A = 2;
        }    }    static class Sub extends Parent {
        public static int B = A;
    }    public static void main (String[] args){
        System.out.println(Sub.B); // 输出结果是父类中的静态变量A的值,也就是2。
    }
  1. The <clinit>() method is not necessary for the class or interface. If there is no static statement block in a class and no variable assignment operation, then the compiler can not generate the <clinit>() method for this class.
  2. Static statement blocks cannot be used in interfaces, but there are still assignment operations for variable initialization, so interfaces and classes will generate <clinit>() methods. But the difference between an interface and a class is that the <clinit>() method of the implementation interface does not need to execute the <clinit>() method of the parent interface first. The parent interface will be initialized only when the variables defined in the parent interface are used. In addition, the implementation class of the interface will not execute the <clinit>() method of the interface when it is initialized.
  3. The virtual machine guarantees that the <clinit>() method of a class is correctly locked and synchronized in a multithreaded environment. If multiple threads initialize a class at the same time, only one thread will execute the <clinit>() of this class. ) Method, other threads need to block and wait until the active thread finishes executing the <clinit>() method. If there is a very time-consuming operation in the <clinit>() method of a class, it may cause multiple processes to block [2]. In practical applications, this blocking is often very hidden.

4. Parental delegation model

4.1 Classes and Class Loaders

  1. Class loader : The code module that implements the action of "getting a binary byte stream describing this class through the fully qualified name of a class" in the class loading stage is called a "class loader". The virtual machine design team puts this action outside the Java virtual machine to implement, so that the application feels how to get the required classes.
  2. For any class, the class loader that loads it and the class itself need to establish its uniqueness in the Java virtual machine . Each class loader has an independent class name space. Comparing whether two classes are "equal" is meaningful only if the two classes are loaded by the same class loader, otherwise, even if the two classes originate from the same Class file and are loaded by the same virtual machine , As long as the class loaders that load them are different, the two classes must be unequal.

4.2 Types of class loaders

From the perspective of the Java virtual machine, there are only two different class loaders:

  1. Start class loader (Bootstrap ClassLoader), the class loader uses the C ++ language, is part of the virtual machine itself .
  2. The other is the loaders of all other classes . These class loaders are implemented by the Java language, independent of the virtual machine, and all inherit from the abstract class java.lang.ClassLoader .

From the perspective of Java developers, class loaders can also be divided into three types of system-provided class loaders and user-defined class loaders.

  1. Bootstrap ClassLoader: Responsible for loading and storing classes in the <JAVA_HOME>\lib directory or in the path specified by the -Xbootclasspath parameter.
  2. Extension class loader (Extension ClassLoader): This loader is implemented by sun.misc.Launcher $ ExtClassLoader, which is responsible for loading <JAVA_HOME> \ lib \ ext directory or system variables are specified java.ext.dirs path For all class libraries, developers can directly use the extended class loader.
  3. Application class loader (Application ClassLoader): This class loader is implemented by sun.misc.Launcher $ App-ClassLoader. Since this class loader is the return value of the getSystemClassLoader() method in ClassLoader, it is generally called the system class loader. It is responsible for loading the class library specified on the user class path (ClassPath). Developers can directly use this class loader. If the application has not customized its own class loader , this is generally the default class in the program Loader .
  4. Custom class loader (User ClassLoader): user-defined class loader. When users write their own class loader, if the loading request needs to be delegated to the bootstrap class loader, just use null instead. To create your own class loader, you only need to inherit the java.lang.ClassLoader class , and then override its findClass (String name) method, that is, specify how to obtain the bytecode stream of the class.
  • If you want to comply with the parental delegation specification, rewrite the findClass method (user-defined class loading logic ).
  • If you want to destroy it, rewrite the loadClass method (the specific logic implementation of the parent delegation) .

The relationship between these class loaders is generally shown in the following figure:

JVM mechanism-the timing of class loading, the process of class loading, parent delegation and the destruction of parent delegation model

 

4.3 Parental delegation model

This hierarchical relationship between the figure shows the class loader, referred to as a class loader parent delegation model (Parents Delegation Model).

  1. The parent delegation model requires that in addition to the top-level startup class loader, all other class loaders should have their own parent class loaders.
  2. The parent delegation model of the class loader was introduced in JDK 1.2. When it is not a mandatory constraint model, it is an implementation of the class loader recommended to developers by Java designers.

The working process of the parent delegation model is:

  1. If a class loader receives a request for class loading , it will not first try to load the class itself, but will delegate the request to the parent class loader to complete.
  2. This is true for every level of class loader, so all class loading requests should ultimately be transmitted to the top class loader .
  3. Only if the parent loader reports that it cannot complete the loading request, the child loader will try to load it by itself .

The parent delegation model can be used to explain a question: Why can't you customize the java.lang.String class?

Answer : Through the parent delegation model, we know: If the user-defined class loader receives a request to load the java.lang.String class and the java.lang.String class has not been loaded, then the custom class loader will load the request Delegated to the parent loader, until it is delegated to the startup class loader (Bootstrcp ClassLoader). The bootable class loader does not recognize the user-defined java.lang.String class, it will only load the java.lang.String class in the JDK.

4.4 Destroy the parent delegation model

As mentioned above, the parent delegation model is not a mandatory constraint model , but a class loader implementation recommended by Java designers to developers. Most class loaders in the Java world follow this model, but there are exceptions. So far, the parental delegation model has mainly experienced 3 large-scale "broken" situations.

  1. For forward compatibility, java.lang.ClassLoader after JDK 1.2 added a new protected method findClass(). Before that, the only purpose of users to inherit java.lang.ClassLoader was to override the loadClass() method, because The virtual machine will call the loader's private method loadClassInternal() when it is loading classes, and the only logic of this method is to call its own loadClass().
  2. The second destruction of the parent delegation model is due to the defects of the model itself, which cannot solve the problem of calling user code for the basic classes.

(1) It is the JNDI service (Java Naming and Directory Interface) . The code of JNDI is loaded by the startup class loader, but the purpose of JNDI is to centrally manage and search resources. It needs to be implemented by independent vendors and deployed in the application. The code of the JNDI interface provider under ClassPath, but the startup class loader may not recognize these codes. So the Java design team introduced: Thread Context ClassLoader. The JNDI service uses this thread context class loader to load the required SPI code, that is, the parent class loader requests the child class loader to complete the class loading action, which destroys the parent delegation model.

(2) For example, tomcat , according to the Java Servlet specification, the Web application's own class priority is required to be higher than the class provided by the Web container. For tomcat, for some unloaded non-basic classes, each web application's own class loader (WebAppClassLoader) will be loaded first, and when it fails to load, it will be handed over to the commonClassLoader to follow the parent delegation model.

  1. The third "destroy" of the parental delegation model is caused by the user's pursuit of the dynamic nature of the program. In order to achieve hot swap, hot deployment, and modularity, it means adding a function or subtracting a function without restarting, only need to replace the module together with the class loader to realize the hot replacement of the code . For example, the emergence of OSGi (Open Service Gateway Initiative). In the OSGi environment, the class loader is no longer a tree structure in the parent delegation model, but is further developed into a network structure.

Guess you like

Origin blog.csdn.net/qq_45401061/article/details/108761241