The initialization process for a class or interface is to execute their initialization method < Clinit >. This method is generated by the compiler at compile time in the class file. It consists of the class static field assignment instruction and the code instruction in the static statement block (static{}), in the same order as in the source code.

Initialization of a class (denoted by C) is triggered when:

  • New (create object), getStatic (get class field), putStatic (assign value to class field), or Invokestatic (call class method) instructions are executed, create an instance of C, get/set C static fields, and call C static methods.

    If the obtained class field is a constant with a ConstantValue attribute, initialization is not triggered

  • The first call to Java. Lang. Invoke. MethodHandle instance returned REF_getStatic, REF_putStatic, REF_invokeStatic, handle REF_newInvokeSpecial type of method.

  • Reflection calls, such as classes in the Class or java.lang.Reflect ‘package

  • If C is a class, the

    method of C is called before the

    method is called by its subclasses

  • If C is an interface and defines a non-abstract, non-static method, its implementation class (directly or indirectly) performs the initialization method

    and initializes C first.

  • C as the main class (containing the main method)

You can see that performing time-consuming operations in static{} causes class initialization to block or even fail

Before the class is initialized, a link operation is performed

To speed up the initialization process, the JVM is multithreaded. Multiple threads may attempt to initialize a class at the same time, or a class initialization process may trigger a recursive initialization process. Therefore, the JVM needs to ensure that only one thread performs the initialization. The JVM keeps the initialization process thread-safe by maintaining a state and a mutex for validated classes.

Virtual machine class status:

  • Class validated and prepared, but not initialized
  • The class is being initialized by a thread
  • The class has been initialized and is ready to use
  • Class initialization failed

In fact, the virtual machine may define more than four states for a class, such as Hotspot, seeabove

In addition to the state, before initializing a class, we first obtain the lock object (monitor) associated with the class, called LC.

The initialization process for class or interface C is as follows (jVM1.8 specification) :

  1. Wait to acquire lock LC for C.

  2. If C is being initialized by another thread, release LC and block the current thread until C initialization is complete.

    Thread interrupts have no effect on initialization

  3. If C is being initialized by the current thread, it must be initialized recursively. Release LC and return as normal.

  4. If C’s state is already initialized, release LC and return as normal.

  5. If C is in a failed initialization state, release LC and raise a NoClassDefFoundError exception.

  6. Otherwise, record the state of the current class C as being initialized, set the current thread to be initialized, and release LC.

    Each final static field in C with a ConstantValue attribute is then initialized in the order in the bytecode file.

    ** Note: The ** JVM specification defines constant assignment during initialization, prior to < Clinit > execution, and the implementation may not strictly adhere to this. For example, the hotspot virtual machine has assigned values to each constant field when creating the _JAVA_mirror mirror class during bytecode parsing.

  7. Next, if C is a class and its parent is uninitialized, SC is called its parent, SI1… SIn is denoted as an interface (directly or indirectly) implemented by C that contains at least one non-abstract, non-static method. SC is initialized first, and the order of all parent interfaces is determined recursively rather than inheritance hierarchically. For an interface I implemented directly by C (in the order of C’s list of interfaces), before I is initialized, I’s parent interfaces are initialized through the loop (in the order of I’s interface list interfaces).

  8. Next, check to see if assertions are turned on (for debugging) in the defining classloader.

    // ClassLoader // check whether assertion is enabled for class // pass# # setClassAssertionStatus (String, Boolean)/setPackageAssertionStatus (String, Boolean) / # setDefaultAssertionStatus (Boolean) set assertions
    boolean desiredAssertionStatus(String className);
    Copy the code
  9. Next, execute C’s initialization method

    .

  10. If the initialization of C completes normally, obtain LC and mark the state of C as completed initialization, wake up all waiting threads, release the lock LC, and the initialization process is complete.

  11. Otherwise, the initialization method must throw an exception e. if E is not the Error or its subclasses, create a ExceptionInInitializerError instance (E) as a parameter, in the next steps, in this instance substitution E, If because of memory can’t create ExceptionInInitializerError instance, replace with an OutOfMemoryError E.

  12. Get the LC, mark C’s initialization status as an error, notify all waiting threads, release the LC, and return via E or some other alternative (see previous step) exception.

The implementation of the virtual machine may optimize this process by canceling the lock acquisition at step 1 (and release at 4/5) when it can determine that the initialization has completed, provided that, according to the Java memory model, all happens-before relationships exist at lock locking and lock optimization.

Here’s an example:

interface IA {
	Object o = new Object();
}

abstract class Base {

	static {
		System.out.println("Base <clinit> invoked");
	}
	
	public Base(a) {
		System.out.println("Base <init> invoked");
	}

	{
		System.out.println("Base normal block invoked"); }}class Sub extends Base implements IA {
	static {
		System.out.println("Sub <clinit> invoked");
	}

	{
		System.out.println("Sub normal block invoked");
	}

	public Sub(a) {
		System.out.println("Sub <init> invoked"); }}public class TestInitialization {

	public static void main(String[] args) {
		newSub(); }}Copy the code

Running on the hotspot VIRTUAL machine:

javac TestInitialization.java && java TestInitialization
Copy the code

It can be seen that the initialization order is: parent static constructor -> subclass static constructor -> parent normal constructor -> parent normal constructor -> subclass normal constructor -> subclass constructor, and the normal constructor is called before the instance constructor, regardless of the order.

The

method was also generated by decompiling the interface because static{} could not be added:

If no instance constructor is defined for the class, the compiler generates a default constructor with no arguments that calls the parent class’s default constructor

If the class does not have an assignment statement or a static code block for a static variable, it does not need to be generated<clinit>

Finally, here are some related interview questions:

  1. What does the following code output?

    public class InitializationQuestion1 {
    
        private static InitializationQuestion1 q = new InitializationQuestion1();
        private static int a;
        private static int b = 0;
    
        public InitializationQuestion1(a) {
            a++;
            b++;
        }
    
        public static void main(String[] args) { System.out.println(InitializationQuestion1.a); System.out.println(InitializationQuestion1.b); }}Copy the code

    How about putting the q statement after the B? Output what?

  2. What does the following code output?

    abstract class Parent {
        static int a = 10;
    
        static {
            System.out.println("Parent init"); }}class Child extends Parent {
        static {
            System.out.println("Child init"); }}public class InitializationQuestion2 {
        public static void main(String[] args) { System.out.println(Child.a); }}Copy the code

    Try the following instead:

    abstract class Parent {
        static final int a = 10;
    
        static {
            System.out.println("Parent init"); }}Copy the code

    Try the following instead:

    abstract class Parent {
        static final int a = value();
    
        static {
            System.out.println("Parent init");
        }
    
        static int value(a){
            return 10; }}Copy the code