Java Initialization Basics

Posted by ScoobyDooobyD00 on Tue, 14 May 2019 16:36:08 +0200

Preface

Initialization in Java includes default initialization, static initialization block, initialization in common initialization blocks and construction methods. What is the final result of the execution of these initialization codes? For many Java developers, it is still confusing. Here, you can learn the loading basics of Java classes to clarify their calling process.

Loading and Linking of Classes

stage Notes
Load Find and load binary data for a class
link Verify: Ensure the correctness of the loaded class; Prepare: Allocate memory for static variables of the class and initialize them to default values; Resolve: Convert symbolic references in the class to direct references
Initialization Assign the correct initial value to the static variable of the class
public static int x; // Default value is 0
public static int num = 10; // The default value is 0 and the initialization value is 10

Class loading refers to reading binaries from the class file of a class into memory, placing them in the method area of the runtime data area, and then creating a java.lang.Class object in the heap area to encapsulate the class's data structure in the method area.

The loader that comes with the Java virtual machine

Loader Name Notes
Root Class Loader Bootstrap, C++ Implementation
Extension Loader The directory specified by the system property java.ext.dirs is loaded by the ExtClassLoader loader, and the class file in the $JAVA_HOME/lib/ext directory is loaded by default if the system property loader is not specified
system class loader Loading classes in the -classpath directory

User-defined class loaders are subclasses of the java.lang.ClassLoader class. Any class is loaded through the class loader, and its Class object contains the ClassLoader instance that loads it.A call to getClassLoader loaded by the root class loader returns null.

System.out.println(String.class.getClassLoader()); // null root class loader
System.out.println(JFrame.class.getClassLoader()); // null root class loader
System.out.println(ClassLoaderTest.class.getClassLoader()); // system class loader

JVM allows a class loader to pre-load a class when it anticipates it will be used. If a class file is missing or has errors during pre-loading, errors will not be reported until the program actively uses it for the first time. If the program never actively uses this class, the class loader will not report errors.

During the connection phase, the connection is to merge class binary data that has already been read into memory into the runtime environment of the virtual machine.
Class validation: mainly includes structure checks for class files; semantics checks to ensure code conforms to Java syntax; byte code validation; binary compatibility validation
Class preparation: During the preparation phase, the Java virtual machine allocates memory for static variables and sets the default initial value
Class parsing: JVM replaces symbol references in the binary data of a class with direct references. Variable names, method names, class names, etc. in the code are symbol references. Direct references are the location of variable method classes in the memory of the method area, that is, their pointers.

Class Initialization

If the class has not been loaded and linked, load and link first; if the class has a direct parent and the parent class is not initialized, initialize the direct parent first; if the class has an initialization statement, execute the initialization statement of the class first, and assign the initial value to the static variable of the class.When a JVM initializes a class, it requires its parent classes to be initialized, but this rule does not apply to interfaces.Initializing a class does not initialize the interface it implements. Initializing an interface does not initialize its parent interface. Initializing a class only counts as an active use of the class if the accessed variable or method is indeed defined in the class.

Java programs use classes actively and passively, and all Java virtual machine implementations must initialize each class or excuse when it is "first actively used" by a Java program.Active use is divided into 6 cases:

  1. Create instances of classes
  2. Accessing static variables of a class or interface
  3. Invoke static methods of classes
  4. Getting classes using reflection
  5. Initialize a subclass of a class
  6. A class marked as a startup class when a Java virtual machine starts, such as Executing java Hello, which is the startup class

Use is passive except in the 6 cases above, and passive use does not perform class initialization.Initializations such as Total cause static code blocks of classes to be executed, and if there are multiple static code blocks, they execute in the order defined in the class.It is important to note that the initialization expression written directly after the class variable is equivalent to the initialization written in the static block.

public class StaticTest {

    private static int a;
    private static boolean b;
    private static float f;

    public static void main(String[] args) {
        System.out.println(StaticTest.a); // 0
        System.out.println(StaticTest.b); // false
        System.out.println(StaticTest.f); // 0.0
    }
}

In the example above, a, b, f static variables have default values because they are set to default values in the preparation phase of the connection and no new values are set in the subsequent initialization phase. Their values are the default values.

public class StaticTest {

    private static int a = 10;

    static {
        f = 30.0f;
    }

    private static float f = 20.1f;
    // Equivalent to 
    // static {
    //     f = 20.1f;
    // }

    static {
        a = 20;
    }

    public static void main(String[] args) {
        System.out.println(StaticTest.a); // 20
        System.out.println(StaticTest.f); // 20.1f
    }
}

In the example above, since the assignment statement f=20.1f is actually equivalent to executing static {f = 20.1f}; since the static initialization code previously assigned 30.0f executes first, the value executed after 20.1f eventually overrides the original value.The previous two examples correspond to the sixth case in which a class is marked as a startup class and its static initialization block is executed.

public class StaticTest {

    public static void main(String[] args) {
        Hello hello; // Nothing will be printed
        System.out.println(Hello.x); // Print 80, but not statements in the Hello static block
    }
}

class Hello {
    static {
        System.out.println("Hello class inited");
    }

    public static final int x = 40 * 2; // Compile-time always bright
}

The first example above declares that a hello variable is not actively used and therefore does not do class initialization. Calling Hello.x is to see that the compiler is always bright and does not belong to accessing static variables, so neither is an active call and does not execute static initialization blocks within the class.

public class StaticTest {

    public static void main(String[] args) {
        new Hello(); // Executes code in a static block
        System.out.println(Hello.x); // Will execute static block always bright
    }
}

class Hello {
    static {
        System.out.println("Hello class inited");
    }

    public static final int x = new Random().nextInt();
}

The above examples, because they create instances of classes and access runtime highlights, are active-use classes that perform initialization of classes.

public class StaticTest {

    public static void main(String[] args) {
        System.out.println(MyHello.x); // Only Hello static initialization blocks will be printed, not MyHello static initialization blocks
    }
}

class Hello {
    static {
        System.out.println("Hello class inited");
    }

    public static final int x = new Random().nextInt();
}


class MyHello extends Hello {
    static {
        System.out.println("My Hello class inited");
    }
}

The example above uses constants of subclasses but constants are actually defined in the parent class, so the subclasses do not perform initialization because they are not actively using subclasses but actively using the parent class.

public class StaticTest {

    public static void main(String[] args) {
        System.out.println(MyHello.y); // Invoke static initialization blocks for parent and child classes
    }
}

class Hello {
    static {
        System.out.println("Hello class inited");
    }

    public static final int x = new Random().nextInt();
}

class MyHello extends Hello {
    static {
        System.out.println("My Hello class inited");
    }

    public static final int y = new Random().nextInt();
}

In the example above, because the runtime constant of the subclass is used, the subclass is used actively, and then the parent class is used actively. The parent class is initialized first, and then the subclass is initialized.

public class Test {
    static {
        System.out.println("Test inited");
    }
}

    public static void main(String[] args) throws ClassNotFoundException {
//        try {
//            Class.forName("myjvm.Test"); //Executes a static initialization block
//        } catch (ClassNotFoundException e) {
//            e.printStackTrace();
//        }
        // Static initialization blocks will not be executed
        StaticTest.class.getClassLoader().loadClass("myjvm.Test");
    }

The above examples demonstrate that active use performs initialization when creating classes with reflection, while passive use when loading classes does not.

Normal Initialization Block

Initializations mentioned in the previous discussion of class loading are the initialization of class variables, that is, the initialization of static class variables.Now let's look at the order in which objects are initialized. A common initialization block is one with no static braces in front.

public class StaticTest {

    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(new Test().x); // 30
    }
}

public class Test {

    {
        x = 20;
        System.out.println("Test object inited");
    }

    public int x = 10;

    static {
        System.out.println("Test inited");
    }

    public Test() {
        x = 30;
    }
}

// Test inited
// Test object inited
// 30

Thus, the object initialization block is not executed until the class initialization block is executed, while the construction method is not executed until the object initialization block is executed.

summary

From the above tests, you can conclude that JVM prepares memory for static variables and assigns default values when loading classes to perform links, performs initialization when programs actively use classes, performs initialization assignments of static variables in the order of static initialization in class definitions, and follows objects defined in classes when users define instances of classes.Initialize block execution, and finally execute the code within the constructor.

Topics: Java jvm