1 Java class loading time

A scenario that causes class loading

  • 1 When an object is created using new
  • When reading or setting class static variables (except compile-time constants)
  • When you make a reflection call to a class using the methods in the java.lang.Reflect package
  • 4 When initializing a class, the system initializes its parent class first, except the interface
  • 5 The main class of the VM startup, that is, the class that defines the main() method, is initialized upon vm startup

A scenario that does not cause class loading

  • For static fields, only the class that directly defines the static field is initialized. Subclass references do not cause subclasses to be initialized, as in the following code
public static void main(String[] args) { System.out.println(Child.NAME); } public static class Base { public static String NAME = "NAME"; static { System.out.println("Base init"); } } public static class Child extends Base { static { System.out.println("Child init"); }}Copy the code

The printed result is:

Base init
NAME
Copy the code
  • Using an array definition to refer to a class does not trigger class initialization. For example:
public static void main(String[] args) { Child[] children = new Child[250]; } public static class Child extends Base { static { System.out.println("Child init"); }}Copy the code

I didn’t print anything

  • References to compile-time constants do not trigger class initialization

Let’s explain what compile-time constants are:

  • First, compile-time constants must be static, because non-static constants cannot be accessed at compile time, and only new objects can be used to initialize the class, so static corresponds to the word “compile-time” in “compile-time constants.

  • Second: compile-time constants must be final, which corresponds to the word “constant” in “compile-time constants.

So static final must be a compile-time constant? Wrong! Such as:

public static final long time = 74110; Public static final long time = system.currentTimemillis (); // Is not a compile-time constant, because the system time is only known at run timeCopy the code

We use code to verify:

public static void main(String[] args) { System.out.println(Init.time); } public static class Init { public static final long time = 74110; Static {system.out.println ("Init is initialized!"); ); }}Copy the code

Running result:

74110
Copy the code

As you can see, the class is not initialized! This is normal, because compile-time constants are put into the constant pool at compile time, and subsequent access to this variable is found in the constant pool, and has nothing to do with the class, so it does not cause initialization. Let’s move on to the second example:

public static void main(String[] args) { System.out.println(Init.time); } public static class Init { public static final long time = System.currentTimeMillis(); Static {system.out.println ("Init is initialized!"); ); }}Copy the code

Running result:

Init is initialized! 1596355938901Copy the code

As you can see, the class is initialized first! Therefore, the value of the third element variable of a compile-time constant needs to be known at compile time. To summarize the definition of a compile-time constant, static final modifiers that are known at compile time are compile-time constants

To find out if a class variable is a compile-time constant, use javac to get the.class file. Then run javap-verbose xxx.class to look directly at the JVM bytecode. If it is a ConstantValue, it is a compile-time constant.

public static class Init {
    public static final long time = 74110;
}
Copy the code

Only part of the corresponding JVM script is posted here:

{ public static final long time; Time descriptor: I flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL; Int 74110 public Init(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 }Copy the code

How about changing time to the current system time:

public static class Init {
    public static final long time = System.currentTimeMillis();
}
Copy the code

The corresponding JVM bytecode is as follows, and only part of it is posted here:

{ public static final long time; Time descriptor: J flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL public static final public Hello(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=2, locals=0, args_size=0 0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 3: putstatic #3 // Field time:J 6: return LineNumberTable: line 4: 0 }Copy the code
  • The parent interface will not be initialized until the interface is initialized, unless a member of the parent interface (such as a member class) is actually used.

Since the interface does not define static blocks, there is no way to verify from the API layer. We will analyze validation from JVM bytecode instructions later.

2 Java class loading mechanism

The class loading mechanism is triggered when the condition of class loading timing is met

Java class loading is divided into five steps: load, connect (verify, prepare, parse), initialize, use, and unload. Let’s go into the details of load, connect, and initialize, and as for use, unload

  • 1 load

Things done during the loading phase:

  • (1) Get the binary byte stream that defines a class by its fully qualified name;
  • (2) Convert the binary flow into the runtime data structure of the method area
  • (3) Use this structure to generate a java.lang.Class object in memory that serves as an access point to the Class

A simple way to think about it is to generate a Java.lang.class object in the method area with the fully qualified name of a Class

  • 2 Connection (the connection phase is split into 3 stages)

    • Validation: Verify that the Class file is valid during loading, such as whether it starts with a magic number and whether the version number is within the scope of the current virtual machine.
    • Preparation: For example, if an object is null, int is zero, Boolean is false, and so on, if it is a “compile time constant”, It’s just the initial value defined.
    • Parse: The process of converting a symbolic reference to a direct reference determines the version of a partial method
  • Initialization: The process of executing the <clinit>() method.

The <clinit> method is obtained by the JVM collecting the “assignment statement” and “static block” of all class variables in the class. If there were no assignment statement and static block of class variables, there would be no <clinit> block.

public class Hello {
    public static final int a = 100;
}
Copy the code

Then use javap-verbose Hello.class to look at the bytecode:

Classfile/Users/lloydfinch/Venn/workspace/Java/test/Hello. The class/path/Last modified on August 2, 2020. The size of 229 bytes / / modify time and size MD5 checksum f32c281d3178ff83100e89e1d092d / 415 / check code Compiled from "Hello. Java"/public/source files Class Hello minor version: 0 class Hello minor version: 0 JDK 11 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #2 // Hello super_class: #3 // java/lang/Object interfaces: 0, fields: 1, methods: 1, attributes: 1 Constant pool: #1 = Methodref #3.#14 // java/lang/Object."<init>":()V #2 = Class #15 // Hello #3 = Class #16 // java/lang/Object #4 = Utf8 a #5 = Utf8 I #6 = Utf8 ConstantValue #7 = Integer 10 #8 = Utf8 <init Utf8 LineNumberTable #12 = Utf8 SourceFile #13 = Utf8 Hello.java #14 = NameAndType #8:#9 // "<init>":()V #15 = Utf8 Hello #16 = Utf8 java/lang/Object { public static final int a; descriptor: I flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 10 public Hello(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 } SourceFile: "Hello.java"Copy the code

<clinit> = “clinit” > = “clinit” > = “clinit” > = “clinit” > = “clinit” > = “clinit” > = “clinit” > = “clinit” > = “clinit” > = “clinit”

public class Hello { public static int a = 100; // Remove final, and this is equivalent to an assignment statement, because final is not an assignment statement, but an "initialization statement"}Copy the code

The corresponding bytecode instruction:

Classfile/Users/lloydfinch/Venn/workspace/Java/test/hello.html class Last modified on August 2, 2020. size 265 bytes MD5 checksum d45f4426aa6b31b54300f59966e46049 Compiled from "Hello.java" public class Hello minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #3 // Hello super_class: #4 // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 1 Constant pool: #1 = Methodref #4.#14 // java/lang/Object."<init>":()V #2 = Fieldref #3.#15 // Hello.a:I #3 = Class #16 // Hello #4 = Class #17 // java/lang/Object #5 = Utf8 a #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 <clinit> <clinit> method #12 = Utf8 SourceFile #13 = Utf8 hello.java #14 = NameAndType #7:#8 :()V #15 = NameAndType #5:#6 // a:I #16 = Utf8 Hello #17 = Utf8 java/lang/Object { public static int a; descriptor: I flags: (0x0009) ACC_PUBLIC, ACC_STATIC public Hello(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=1, locals=0, args_size=0 0: bipush 10 2: putstatic #2 // Field a:I 5: return LineNumberTable: line 4: 0 } SourceFile: "Hello.java"Copy the code

We can see that the <clinit>() method is added. If the code is changed to this:

public class Hello { public static final int a; static { a = 100; }}Copy the code

The result is the same, with the same <clinit>() statement

The <clinit>() method is generated based on the order in which statements appear in the source file. Static blocks can only access variables defined before them, and those defined after them can only be assigned and not accessed!

public static class Init { public static int a = 10; static { a = 20; / / to the System. The out. Println (a); // B = 10; / / to the System. The out. Println (b); } public static int b = 20; public static int b = 20; }Copy the code

The JVM automatically calls the parent <clinit>() method before the subclass <clinit>() executes. This means that the parent static statement executes before the subclass assignment variable statement, so the java.lang.object <clinit>() method is always called first

public static void main(String[] args) { System.out.println(Child.b); } public static class Child extends Base { public static int b = a; static { System.out.println("Child Init"); } } public static class Base { public static int a = 10; Static {system.out.println (" assign 10"); a = 20; }}Copy the code

Running result:

Assign 10 Child Init 20Copy the code

You can see that the final result is 20, not 10

The <clinit>() method is not required for a class or interface. If there are no assignment statements or static blocks for class variables in the class, there will be none. The <clinit>() method of the interface is not called before the <clinit>() method of the interface is called, and is initialized only when a variable defined by the parent interface is used

The JVM ensures that the <clinit>() method is properly locked and synchronized across multiple threads, and you can use this feature to implement the singleton pattern, or static inner class singletons. Such as:

public class SingleInstance { private static SingleInstance instance; private SingleInstance() { } public static SingleInstance getInstance() { return Inner.instance; } private static class <clinit>() <clinit>() <clinit>() <clinit>(); Private static SingleInstance instance = new SingleInstance(); }}Copy the code

The method call

In the “parse” phase of the “connect” phase, we determine the version of some methods, such as overloaded versions. Here’s an example:

public static void main(String[] args) {
    TestClass testClass = new TestClass();
    Base base = new Child();
    testClass.info(base);
}

public static class Child extends Base {
}

public static class Base {
}

public void info(Base base) {
    System.out.println("info base");
}

public void info(Child child) {
    System.out.println("info child");
}
Copy the code

Running result:

info base
Copy the code

That is, function overloading is determined at compile time and is called “statically dispatched” in the JVM.

Here’s another example:

public static void main(String[] args) { Base base = new Child(); base.info(); } public static class Child extends Base { @Override public void info() { System.out.println("Child"); } } public static class Base { public void info() { System.out.println("Base"); }}Copy the code

Running result:

child
Copy the code

As everyone knows, this result is a manifestation of polymorphism, known as overwriting, which proves that function overwriting is “dynamically dispatched” in the JVM.

conclusion

  • A compile-time constant is a static final decorated variable whose value is determined at compile time and is marked ConstantValue in the JVM instruction
  • The preparation phase allocates memory and assigns an initial value to the class variable, which is the specified value if it is a compile-time constant, or zero if it is not
  • The 3

    () method ensures that the parent class executes first and is thread-safe and can be used to implement static inner class singletons
  • Method overloads are statically allocated, method overrides are dynamically allocated
  • Class 5 variables have two phases of assignment, one is the preparation phase and one is the initialization phase. Compile time constants are correctly assigned during the preparation phase and non-edit time constants are correctly assigned during the initialization phase