Class connection and initialization (with a class initialization interview question), initialize the question
By loading, connecting, and initializing the Java virtual machine, a Java type can be used by Java programs, as shown in. The connection process consists of three parts: verification, preparation, and resolution. The parsing process of the central classification can be postponed until the program uses a symbolic reference.
The parsing process can be postponed until the class is initialized, but this is conditional. the Java VM must initialize each class or interface when it is actively used.
The following are active scenarios:
(1). (whether created directly through new, reflection, cloning, or serialization) create a new instance of a class
(2). Use static methods of a class
(3) Access static fields of a class or interface
(4). Call some reflection methods in Java API
(5) initialize a subclass of A Class (requires that all its ancestor classes be initialized; otherwise, the inherited members cannot be accessed correctly)
(6) Start a class marked as a startup class (including the main () method)
Active use will lead to class initialization, and its superclasses will be initialized before the class initialization, but when accessing the static fields or methods of the parent class through the subclass, for sub-classes (or sub-interfaces and interface implementation classes), such access is passive access, or access to static members not declared in this class (interface.
For example:
Grandpa is defined as follows:
1 package com.ice.passiveaccess;2 3 public class Grandpa {4 static{5 System.out.println("Grandpa was initialized.");6 }7 }
Parent is defined as follows:
1 package com.ice.passiveaccess;2 3 public class Parent extends Grandpa{4 static String language = "Chinese";5 static{6 System.out.println("Parent was initialized.");7 }8 }
Cindy is defined as follows:
1 package com.ice.passiveaccess;2 3 public class Cindy extends Parent{4 static{5 System.out.println("Child was initialized.");6 }7 }
Now access the language Member of the parent class through Cindy
1 package com.ice.passiveaccess;2 3 public class PassiveAccessTest {4 public static void main(String args[]){5 System.out.println(Cindy.language);6 }7 }
The result is as follows:
It can be seen that this is a passive access, and Cindy itself is not initialized.
The following describes the loading, verification, and initialization processes:
1. Load:
(1) Find the class file of this type and generate a binary data stream of the class file of this type (loadClassData () method to be implemented by ClassLoader)
(2) parse the binary data stream as the data structure in the Method Area
(3) create a java. lang. Class instance of this type
In the relevant code of the loader, we can see that a Java type object (Class Object) is created through defineClass ).
2. Verification:
The class file validator requires four independent scans to complete the verification:
The first scan is performed during loading, and the structure of the class file is checked, as shown in figure
(1) Check the magic number to determine whether the file is a normal class file
(2) Check the Primary and Secondary version numbers to determine whether the class file is compatible with the Java Virtual Machine.
(3) Check the length and type of the class file to avoid missing or appended content of the class file.
During the connection process, the second scan performs a semantic check on the type of data, mainly checks the binary compatibility of each class (mainly checks the relationship between the super class and the sub-class) and whether the class itself meets the specified semantic conditions
(1). The final class cannot have child classes.
(2) The final method cannot be overwritten (overwrite)
(3). No incompatible method declaration between subclass and super class
(4). Check whether the entry type of the constant pool is consistent (for example, whether the content of the CONSTANT_Class constant pool points to a CONSTANT_Utf8 String constant pool)
(5). Check all the special strings of the constant pool to determine whether they are instances of the type they belong to, and whether they comply with the specific context-independent syntax and format.
The third scan is bytecode verification. The verification content and implementation are complex. It mainly checks whether the bytecode can be safely executed by the Java Virtual Machine.
The fourth scan is carried out during parsing to verify the symbol reference. During the dynamic connection process, when you replace a symbolic reference with a direct reference by searching for referenced classes, interfaces, fields, and methods stored in the constant pool, first, check whether the queried element exists. Then, check the access permission and whether the queried element is a static class member rather than an instance Member.
3. Preparation:
Allocate memory for class variables and set the default initial values (the initial value of the memory is set, rather than the actual initialization of class variables, that is, declare int I = 5 in the class, but in fact, the memory is allocated and the initial value is set to 0)
4. Resolution:
Search for symbolic references of classes, interfaces, fields, and methods in the constant pool of classes, and replace these symbolic references with direct references.
5. Initialization:
Assign the specified initial value to the class variable (in this case, int I = 5 must be given to I with the initial value 5 ). There are two ways to give this initial value. One is through the initialization Statement of class variables, and the other is the static initialization statement. These initialization statements will be put together by the Java compiler in the method.
As described above, a class needs to initialize its direct superclass and recursively initialize its ancestor class. Initialization is completed by calling the class initialization method. In addition, the interface does not need to initialize its parent interface, but only needs to execute the interface initialization method of this interface.
Note:
(1 ). in the initialization phase, only class variables (static global variables) are initialized. When the class variable declaration is final type switch initialization statement, the constant expression is used to initialize and assign values, it will not be initialized, it will be directly calculated by the compiler and saved in the constant pool, and the use of these variables will directly embed their variable values into the bytecode.
For example, the UsefulParameter class is as follows:
Class UsefulParameter{ static final int height = 2; static final int width = height * 2; }
The class variable initialization of the class Area is as follows:
Class Area{ static int height = UsefulParameter.height * 2 ; static int width = UsefulParameter.width * 2; }
In Area <clinit>, 2 and 4 are directly embedded into the bytecode.
(2 ). the initialization of an interface is different from that of a class. In the initialization phase, all public, static, and final fields declared in the interface cannot be compiled as constants.
6. Class instantiation
Here we need to understand what is class initialization and what is class instantiation
As described above, when class initialization is performed, the specified initial value is assigned to the class (static) variable. After class initialization, the static fields and methods of the category class can be accessed, while the non-static (Instance) of the category class) fields and methods, you need to create an object instance of the class. Therefore, class instantiation is to create an object of this class on the stack after class initialization.
The static methods and fields of the class belong to the class. The life cycle of the data stored in the method area depends on the class, and the instance methods and fields are located in the Java heap, the lifecycle of an object depends on its lifecycle.
Class initialization starts from the ancestor class to the subclass, and initializes the initialization statements and static initialization statement blocks of class variables in sequence. The initialization of the class instance is similar. The class member initialization statements, instance initialization blocks, and constructor are initialized in sequence from the ancestor class to the subclass.
For example:
1 package com. ice. init; 2 3 public class Parent {4 public static int I = print ("parent static: I"); 5 public int ii = print ("parent: ii "); 6 7 static {8 print ("parent class static initialization"); 9} 10 11 {12 print ("parent class instance initialization "); 13} 14 15 public Parent (String str) {16 System. out. println ("parent constructor:" + str); 17} 18 19 public static int print (String str) {20 System. out. println ("initial:" + str); 21 return I; 22} 23}
Child:
1 package com. ice. init; 2 3 public class Child extends Parent {4 public static int I = print ("child static: I"); 5 public int ii = print ("child: ii "); 6 7 static {8 print ("subclass static initialization"); 9} 10 11 {12 print ("subclass instance initialization"); 13} 14 15 public Child (String str) {16 super (str); 17 System. out. println ("Child constructor:" + str); 18} 19 20 public static int print (String str) {21 System. out. println ("initial:" + str); 22 return I; 23} 24 25 public static void main (String args []) {26 Child child = new Child ("cindy"); 27} 28}
The initialization sequence is as follows:
The Java compiler generates at least one instance initialization method for each class. One <init> method is divided into three parts: the other initialization method <init> (), the bytecode of the initialization method body of any instance Member.
<Init> the method is called as follows:
If <init> specifies that another constructor is called from this () method, another constructor is called. Otherwise, if the class has a direct superclass, if <init> specifies that the superclass constructor is explicitly called from the super () method, the constructor of the superclass is called. Otherwise, the non-argument constructor of the superclass is called by default. In this way, the initialization of the corresponding instance members will be completed from the ancestor class to the class (the quilt class may overwrite)
Next, end this section with a question:
Judgment output:
1 package com. ice. init; 2 3 class T implements Cloneable {4 public static int k = 0; 5 public static T t1 = new T ("t1 "); 6 public static T t2 = new T ("t2"); 7 public static int I = print ("I"); 8 public static int n = 99; 9 10 public int j = print ("j"); 11 {12 print (""); 13} 14 15 static {16 print (" "); 17} 18 19 public T (String str) {20 System. out. println (++ k) + ":" + str + "I =" + I + "n =" + n); 21 ++ n; ++ I; 22} 23 24 public static int print (String str) {25 System. out. println (++ k) + ":" + str + "I =" + I + "n =" + n); 26 ++ n; 27 return ++ I; 28} 29 30 public static void main (String [] args) {31 T = new t ("init"); 32} 33}
The problem is as follows:
(1). Class T is first loaded and connected before initialization. Fields k, t1, t2, I, n, and static blocks are initialized first. (2 ). instance t1 initialization initializes instance member j. (In fact, initialization of the content of the parent class instance is performed first.) Call the static method print and execute the instance initialization block {}. Output: WORKER 1: j I = 0 n = 0 (I and n are not initialized yet) limit 2: Construct block I = 1 n = 1 (3) then call the constructor of instance t1, and output: required 3: t1 I = 2 n = 2 (4 ). similar to initialization with t2 instances: required 4: j I = 3 n = 3 blocks 5: constructed block I = 4 n = 4 blocks 6: t2 I = 5 n = 5 (5 ). I initialization: ipv7. I I = 6 n = 6 (6 ). n initialization and static block initialization: Listen 8. static block I = 7 n = 99 (n has been initialized) (7 ). t instance initialization: Listen 9.j I = 8 n = 100 then 10. construct block I = 9 n = 101 limit 11. init I = 10 n= 102View Code