When does Java trigger the initialization of a class?

When does Java trigger initialization of a class?

  • Create object usingnewkeyword
  • Access the static member variables of the class or assign values ​​to the static member variables of the class
  • Call a static method of a class
  • When calling a class by reflection, such asClass.forName()
  • When initializing a subclass, its parent class will be initialized first (if the parent class has not been initialized yet)
  • When encountering a startup class, if a class is marked as a startup class (that is, it contains the main method), the virtual machine will initialize the main class first.
  • When a class that implements an interface with a default method is initialized (a class with an interface method modified by the default keyword)
  • When using the new dynamic language support added in JDK7MethodHandle

When does the virtual machine load the class?

Regarding the circumstances under which the first stage of class loading needs to be started, the "Java Virtual Machine Specification" does not impose any mandatory constraints, leaving it to the virtual machine to play freely. But for the initialization phase, the virtual machine specification strictly stipulates: if and only if the following six situations occur, the class must be initialized immediately, and loading, verification, and preparation naturally need to be performed before this. The behavior in these six scenarios in the virtual machine specification is called active referenceof a type. In addition, all methods of reference types will not trigger initialization, which are called passive references.

1. When encountering the specified command

During the execution of the program, encounternew, getstatic, putstatic, invokestatic When these four bytecodes are executed, if the type does not Initialization, you need to trigger its initialization phase first.

new

There is nothing to say. Creating an object using the new keyword will definitely trigger the initialization of the class.

getstatic 与 putstatic

When accessing a static variable of a class or interface, or assigning a value to the static variable, the initialization of the class will be triggered. Let’s look at the first example first:

// 示例1
public class Demo {
    
    
    public static void main(String[] args) {
    
    
        System.out.println(Bird.a);
    }
}

class Bird {
    
    
    static int a = 2;
    // 在类初始化过程中不仅会执行构造方法,还会执行类的静态代码块
    // 如果静态代码块里的语句被执行,说明类已开始初始化
    static {
    
    
        System.out.println("bird init");
    }
}

After execution, it will output:

bird init
2

Similarly, if you directly assign a value to Bird.a, it will also trigger the initialization of the Bird class:

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Bird.a = 2;
    }
}

class Bird {
    
    
    static int a;
    static {
    
    
        System.out.println("bird init");
    }
}

After execution, it will output:

bird init

Then look at the following example:

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Bird.a = 2;
    }
}

class Bird {
    
    
    // 与前面的例子不同的是,这里使用 final 修饰
    static final int a = 2;
    static {
    
    
        System.out.println("bird init");
    }
}

There will be no output after execution.

In this example, a is no longer a static variable, but becomes a constant. After running the code, it is found that the Bird class is not triggered. initialization process. Constants will be stored in the constant pool of the class where the method calling this constant is located during the compilation phase. Essentially, the calling class does not directly reference the class that defines the constant, so it does not trigger the initialization of the class that defines the constant. That is, the constant a=2 has been stored in the constant pool of the Demo class. After that, the Demo class and The Bird class has nothing to do with it anymore. You can even directly delete the file generated by the Bird class, and still works. normal operation. Use the command to decompile the bytecode:classDemojavap

// 前面已省略无关部分
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: iconst_2
       4: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
       7: return
}

You can see from the decompiled code:Bird.a has become a mnemonic iconst_2 (replace intType2 is pushed to the top of the stack) and has no connection with the Bird class. This also proves from the side that only Only accessing static variables of a class will trigger the initialization process of the class, not other types of variables.

Regarding Java mnemonics, if the constant in the above example is modified to a different value, different mnemonics will be generated, such as:

// bipush  20
static int a = 20; 
// 3: sipush        130
static int a = 130
// 3: ldc #4   // int 327670
static int a = 327670;

Among them:
iconst_n: Push the int type numbern to the top of the stack, nGet value0~5
lconst_n: push long type numbern to the top of the stack, n get it Value0,1, similar ones include fconst_n, dconst_n
bipush: convert single-byte constant value (-128~127) Push to the top of the stack
sipush: Push a short integer constant value (-32768~32767) to the top of the stack
ldc: Push int, float or String type constant value is pushed from the constant pool to the top of the stack

Let’s look at the next example:

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        System.out.println(Bird.a);
    }
}

class Bird {
    
    
    static final String a = UUID.randomUUID().toString();
    static {
    
    
        System.out.println("bird init");
    }
}

After execution, it will output:

bird init
d01308ed-8b35-484c-b440-04ce3ecb7c0e

In this example, the value of the constanta cannot be determined at compile time, and a method call is required. In this case, getstaticInstruction will also trigger the initialization of the class, so it will outputbird init. Take a look at the code after decompiled bytecode:

// 已省略部分无关代码
public static void main(java.lang.String[]);
  Code:
    0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    3: getstatic     #3                  // Field com/hicsc/classloader/Bird.a:Ljava/lang/String;
    6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    9: return

invokestatic

When a static method of a class is called, the initialization of the class is also triggered. for example:

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Bird.fly();
    }
}

class Bird {
    
    
    static {
    
    
        System.out.println("bird init");
    }
    static void fly() {
    
    
        System.out.println("bird fly");
    }
}

After execution, it will output:

bird init
bird fly

This example can prove that calling the static method of the class will indeed trigger the initialization of the class.

2. When calling reflection

When using the method of the java.lang.reflect package to make a reflection call on a type, if the type has not been initialized, its initialization needs to be triggered first. Consider the following example:

ublic class Demo {
    
    
    public static void main(String[] args) throws Exception {
    
    
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class clazz = loader.loadClass("com.hicsc.classloader.Bird");
        System.out.println(clazz);
        System.out.println("——————");
        clazz = Class.forName("com.hicsc.classloader.Bird");
        System.out.println(clazz);
    }
}

class Bird {
    
    
    static {
    
    
        System.out.println("bird init");
    }
}

Output result after execution:

class com.hicsc.classloader.Bird
------------
bird init
class com.hicsc.classloader.Bird

In this example, calling theClassLoadermethodload of a class will not trigger the initialization of the class. Instead, use the forName method triggers the initialization of the class.

3. When initializing a subclass

When initializing a class, if it is found that its parent class has not been initialized, you need to trigger the initialization of its parent class first. For example:

public class Demo {
    
    
    public static void main(String[] args) throws Exception {
    
    
        Pigeon.fly();
    }
}

class Bird {
    
    
    static {
    
    
        System.out.println("bird init");
    }
}

class Pigeon extends Bird {
    
    
    static {
    
    
        System.out.println("pigeon init");
    }
    static void fly() {
    
    
        System.out.println("pigeon fly");
    }
}

Output after execution:

bird init
pigeon init
pigeon fly

In this example, when the main method calls the static method of the Pigeon class, the parent class is initialized firstBird, and then the subclassPigeon. Therefore, when a class is initialized, if it is found that its parent class has not been initialized, the initialization of the parent class will be triggered first.

Calling a static method that exists in the parent class on a subclass will only trigger the initialization of the parent class but not the initialization of the subclass.

Looking at the example below, you can guess the running result first:

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Pigeon.fly();
    }
}

class Bird {
    
    
    static {
    
    
        System.out.println("bird init");
    }
    static void fly() {
    
    
        System.out.println("bird fly");
    }
}

class Pigeon extends Bird {
    
    
    static {
    
    
        System.out.println("pigeon init");
    }
}

Output:

bird init
bird fly

In this example, since the fly method is defined in the parent class, the owner of the method is the parent class. Therefore, using Pigeno.fly() is not It represents an active reference to the subclass, but it represents an active reference to the parent class, so it will only trigger the initialization of the parent class.

4. When encountering startup class

When the virtual machine starts, if a class is marked as a startup class (ie: contains the main method), the virtual machine will first initialize the main class. For example:

public class Demo {
    
    
    static {
    
    
        System.out.println("main init");
    }
    public static void main(String[] args) throws Exception {
    
    
        Bird.fly();
    }
}

class Bird {
    
    
    static {
    
    
        System.out.println("bird init");
    }
    static void fly() {
    
    
        System.out.println("bird fly");
    }
}

Output after execution:

main init
bird init
bird fly

5. When a class that implements an interface with a default method is initialized

When an interface is defined the newly added default method of JDK8 (the interface method modified by the default keyword), If there is an implementation class of this interface that is initialized, then the interface must be initialized before it .

Since there is nostatic{} code block in the interface, how to determine whether an interface is initialized? Consider the following example:

public class Demo {
    
    
    public static void main(String[] args) throws Exception {
    
    
        Pigeon pigeon = new Pigeon();
    }
}

interface Bird {
    
    
    // 如果接口被初始化,那么这句代码一定会执行
    // 那么Intf类的静态代码块一定会被执行
    public static Intf intf = new Intf();
    default void fly() {
    
    
        System.out.println("bird fly");
    }
}

class Pigeon implements Bird {
    
    
    static {
    
    
        System.out.println("pigeon init");
    }
}

class Intf {
    
    
    {
    
    
        System.out.println("interface init");
    }
}

Output after execution:

interface init
pigeon init

It can be seen that the interface has indeed been initialized. If the default method in the interface is removed, then interface init will not be output, that is, the interface has not been initialized.

6. When using the new dynamic language support added in JDK7

When using the newly added dynamic type language support of JDK7, if the final parsing result of an java.lang.invoke.MethodHandle instance is REF_getStatic, REF_putStatic, REF_invokeStatic, REF_newInvokeSpecial four types of method handles, and the class corresponding to this method handle has not been initialized, you need to trigger its initialization first.

To put it simply,when the instance is called for the first timeMethodHandle, if the class where the method it points to has not been initialized, it needs to be triggered first Its initialization.

What is a dynamically typed language:

  • The key feature of a dynamically typed language is that the main process of type checking is performed during runtime. Common languages ​​such as JavaScript, PHP, Python, etc. In contrast, languages ​​that perform type checking during compilation are statically typed languages. , such as Java and C#, etc.
  • To put it simply, for dynamically typed languages, variables have no type, only the value of the variable has a type. At compile time, the compiler can only determine the name, parameters, and return value of the method, but will not confirm the value returned by the method. Concrete types and parameter types.
  • However, statically typed languages ​​such as Java are different. If you define an integer variable x, then the value of Whether the value types are consistent, otherwise the compilation will not pass. Therefore, "variables have no type but variable values ​​have types" is a core feature of dynamically typed languages.

For the difference betweenMethodHandle and reflection, you can refer to Section 8.4.3 of "In-depth Understanding of Java Virtual Machine" by Zhou Zhiming. Part of the content is quoted here for easier understanding.

  1. The Reflection and MethodHandle mechanisms essentially simulate method calls, but Reflection simulates method calls at the Java code level, while MethodHandle simulates method calls at the bytecode level.
  2. The Method object in reflection contains various information such as method signature, descriptor, method attribute list, execution permission, etc., while MethodHandle only contains information related to executing the method. In layman's terms: Reflection is heavyweight, while MethodHandle is lightweight. .

In general, reflection is for the Java language, while MethodHandle is available for all languages ​​on the Java virtual machine.

Let's look at a simple example:

public class Demo {
    
    
    public static void main(String[] args) throws Exception {
    
    
        new Pigeon().fly();
    }
}

class Bird {
    
    
    static {
    
    
        System.out.println("bird init");
    }

    static void fly() {
    
    
        System.out.println("bird fly");
    }
}

class Pigeon {
    
    
    void fly() {
    
    
        try {
    
    
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            // MethodType.methodType 方法的第一个参数是返回值
            // 然后按照目标方法接收的参数的顺序填写参数类型
            // Bird.fly() 方法返回值是空, 没有参数
            MethodType type = MethodType.methodType(void.class);
            MethodHandle handle = lookup.findStatic(Bird.class, "fly", type);
            handle.invoke();
        } catch (Throwable a) {
    
    
            a.printStackTrace();
        }
    }
}

In the Pigeon class, use MethodHandle to call the static method in the Bird classfly, as mentioned before, when calling an instance ofMethodHandle for the first time, if the class of the method it points to has not been initialized, its initialization needs to be triggered first. Therefore, the static code block in theBird class will definitely be executed here. And the final running results are consistent with our expectations:

bird init
bird fly

How does the virtual machine load classes - class loading process

The whole process of class loading includes:loading, verification, preparation, parsingandinitialization a> 5 stages, it is a very complicated process.

Insert image description here

Insert image description here

Loading Loading

The Loading stage mainly finds the class file of the class, reads the binary byte stream in the file into the memory, and then creates a java.lang.Class object in the memory.

After the loading is completed, it enters the connection phase, but it should be noted that Some actions in the loading phase and the connection phase (such as some words Section code file format verification action) is interleaved. The loading phase has not yet been completed. The connection phase may have started, but these are sandwiched between the loading phase. The actions are still part of the connection phase. The start times of these two phases still maintain a fixed sequence. That is, only after the loading phase starts, it is possible to enter the connection phase.

Verification

Verification is the first step in the connection phase. Its purpose is to ensure the correctness of the loaded class, that is, to ensure that the loaded byte stream information conforms to the "Java Virtual Machine All constraint requirements of the Specification ensure that this information will not endanger the security of the virtual machine itself after being run as code.

In fact, a lot of security checks have been done during the compilation process of Java code. For example, an object cannot be converted to its unimplemented type, uninitialized variables cannot be used (except for assignment), and non-existent code cannot be jumped. OK wait. But the JVM still needs to verify these operations, because the Class file is not necessarily compiled from Java source code, and you can even type it out yourself through the keyboard. If the JVM does not perform verification, it is likely that the entire system will be attacked or crash due to loading of incorrect or malicious byte streams. Therefore, verifying bytecode is also a necessary measure for the JVM to protect itself.

The entire verification phase includesverification of file format, metadata, bytecode, symbol references and other information.

Preparation

This stage mainly involves allocating memory for static variables of the class and initializing them to default values. There are two points to note here:

  • Only the static variables of the class are allocated and initialized, and the instance variables are not included.
  • is initialized to the default value, for example, int is 0, and the reference type is initialized to null

It should be noted that the main purpose of the preparation phase is not to initialize, but to allocate memory for static variables and then fill in an initial value. For example:

// 在准备阶段是把静态类型初始化为 0,即默认值
// 在初始化阶段才会把 a 的值赋为 1
public static int a = 1;

Let’s look at an example to deepen our impression. You can first consider the running results.

public class StaticVariableLoadOrder {
    
    
    public static void main(String[] args) {
    
    
        Singleton singleton = Singleton.getInstance();
        System.out.println("counter1:" + Singleton.counter1);
        System.out.println("counter2:" + Singleton.counter2);
    }
}

class Singleton {
    
    

    public static Singleton instance = new Singleton();

    private Singleton() {
    
    
        counter1++;
        counter2++;
        System.out.println("构造方法里:counter1:" + counter1 + ", counter2:" + counter2);
    }

    public static int counter1;
    public static int counter2 = 0;

    public static Singleton getInstance() {
    
    
        return instance;
    }
}

The result of its operation is:

构造方法里:counter1:1, counter2:1
counter1:1
counter2:0

In the preparation phase, counter1 and counter2 are initialized to default values0, therefore, after incrementing in the constructor, their values ​​become 1, and then continue to perform initialization, only counter2 is assigned the value remain unchanged. 0,counter1

If you understand this code, look at the example below and think about what will be output?

// main 方法所在类的代码不变
// 修改了 counter1 的位置,并为其初始化为 1
class Singleton {
    
    
    public static int counter1 = 1;
    public static Singleton instance = new Singleton();

    private Singleton() {
    
    
        counter1++;
        counter2++;
        System.out.println("构造方法里:counter1:" + counter1 + ", counter2:" + counter2);
    }

    public static int counter2 = 0;
    public static Singleton getInstance() {
    
    
        return instance;
    }
}

Output after running:

构造方法里:counter1:2, counter2:1
counter1:2
counter2:0

counter2There is no change in , why does the value of counter1 change to 2? In fact, it isbecause when the class is initialized, it comes in the order of the code. For example, in the above example, it iscounter1Assignment and construction method execution are both executed in the initialization phase, but who comes first and who comes last? In order, therefore, when executing the constructor method, counter1 has been assigned to 1. After executing the auto-increment, it will naturally become 2.

Analysis Resolution

The resolution phase isthe process of replacing symbolic references to constant pool classes with direct references. At compile time, Java classes do not know the actual address of the referenced class and can only use symbolic references instead. Symbol references are stored in the constant pool of the class file, such as fully qualified names of classes and interfaces, class references, method references, member variable references, etc. If you want to use these classes and methods, you need to They are converted into memory addresses or pointers that the JVM can obtain directly, that is, direct references.

Therefore, the parsing action is mainly performed on seven types of symbol references: classes or interfaces, fields, class methods, interface methods, method types, method handles, and call site qualifiers.

Initialization

In thepreparation phase we just set the static variables An initial value similar to 0 is set. At this stage, the class variable and Other resources.

To put it more intuitively the initialization process is the process of executing the class constructor<clinit> method.

Class initialization is the last step in the class loading process. It is not until this step that the JVM actually starts executing the Java code written in the class. After initialization, it is almost the whole process of class loading. When initialization is needed, it is the several situations we mentioned earlier.

Class initialization is lazy, which will not lead to class initialization, which is the passive reference type mentioned earlier. Let’s talk more about it:

  • Accessing class's static final static constants (basic types and strings) will not trigger initialization
  • Accessing class objects.classwill not trigger initialization
  • Creating an array of this class does not trigger initialization
  • Executing the class loader's loadClass method will not trigger initialization
  • Class.forNameWhen (reflection) parameter 2 isfalse (it will be initialized only when true is)

When compiling and generating the class file, the compiler will generate two methods to add to the class file, One is the initialization method of the classclinit, the other is the initialization method of the instanceinit.

1. Class initialization method:<clinit>()

  • During the compilation process, the Java compiler will automatically collect allstatic variable assignment statements,Statementin the static code block, merge itinto the class constructor<clinit>()method,The order of collection is determined by the order in which they appear in the source code files. Class initialization methods are generally executed during the class initialization phase.
  • If there is a parent-child relationship between two classes, then before executing the <clinit>() method of the subclass, it will be ensured that the method of the parent class has been executed. Therefore, The static code block of the parent class will take precedence over the static code block of the subclass.

example:

public class ClassDemo {
    
    
    static {
    
    
        i = 20;
    }
    static int i = 10;
    static {
    
    
        i = 30;
    }
   // init 方法收集后里面的代码就是这个,当然你是看不到该方法的
    init() {
    
    
      i = 20;
      i = 10;
      i = 30;
    }
}
  • <clinit>()The method does not need to be called explicitly. It will be called immediately after the class is parsed, and the parent class's <clinit>() will always be executed before the subclass's, so the first one to be executed in the jvm must be method in Object. <clinit>()
  • <clinit>()Methods are not necessary, if there are no static code blocks and variable assignments, there will be no
  • The interface also has variable copy operations, so <clinit>() will also be generated, but it will only be initialized when the variables defined in the parent interface are used.

One thing that needs special emphasis here is thatJVM will ensure that a class's<clinit>()methods are correctly locked and synchronized in a multi-threaded environment a>, if multiple threads initialize a class at the same time, then only one of the threads will execute the <clinit>() method of this class, and other threads need to wait until The a><clinit>() method is executed. If there are long-running operations in the <clinit>() method of a class, multiple threads may be blocked. In actual applications, this blocking is often very hidden. Therefore, in the actual development process, we will emphasize not to add too much business logic to the construction method of the class, or even some very time-consuming operations.

In addition,In a static code block, only variables before it is defined can be accessed. Variables defined after it can be assigned values ​​but cannot be accessed:

class Class{
    
    
    static {
    
    
        c = 2; // 赋值操作可以正常编译通过
        System.out.println(c);//编译器提示 Illegal forward reference,非法向前引用
    }
    static int c = 1;
}

2. Object initialization method:init()

  • init() is a method automatically generated by the instance object. The compiler willcollect"class member variables" =4>'sassignment statement, ordinary code block, and finally collectconstructorThe code finally consists ofobject initialization method. The object initialization method is generally executed when instantiates a class object.

example:

public class ClassDemo {
    
    
    int a = 1;
    {
    
    
        a = 2;
        System.out.println(2);
    }
    {
    
    
        b = "b2";
        System.out.println("b2");
    }
    String b = "b1";
    public ClassDemo(int a, String b) {
    
    
        System.out.println("构造器赋值前:"+this.a+" "+this.b);
        this.a = a;
        this.b = b;
    }
    public static void main(String[] args) {
    
    
        ClassDemo demo = new ClassDemo(3, "b3");
        System.out.println("构造结束后:"+demo.a+" "+demo.b);
//        2
//        b2
//        构造器赋值前:2 b1
//        构造结束后:3 b3
    }
}

The above codeinit() method is actually:

public init(int a, String b){
    
    
	super(); // 不要忘记在底层还会加上父类的构造方法
	this.a = 1;
	this.a = 2;
	System.out.println(2);
	this.b = "b2";
	System.out.println("b2");
	this.b = "b1";
	System.out.println("构造器赋值前:" + this.a + " " + this.b); // 构造方法在最后
	this.a = a;
	this.b = b;
}

Summary of class execution process:

  1. Determine the initial value of the class variable. During the preparation phase of class loading, JVM will initialize the default value for the "class variable". At this time, the class variable will There is an initial zero value. If it is a class variable modified by final, it will be initialized directly to the value the user wants.
  2. Initialization entry method. When entering the initialization phase of class loading, JVM will search for the entire main method entrance, thereby initializing the location where the main method is located. Entire class. When a class needs to be initialized, the class constructor is initialized first, and then the object constructor is initialized.
  3. Initialization class constructor. The JVM willcollect the assignment statements and static code blocks of "class variables" in order, and form them into aclass constructor , which is eventually executed by the JVM.
  4. Initialize object constructor. The JVM willcollect assignment statements of "class member variables", ordinary code blocks in order, and finally collectconstructor methods< /span>, which is eventually executed by the JVM. object constructor, they are composed into

If when initializing "Class variable", the class variable is an object reference of another class , then load the corresponding class first, then instantiate the class object , and then continue to initialize other class variables.


reference:

Guess you like

Origin blog.csdn.net/lyabc123456/article/details/134914637