In the last article we learned that the Java language types fall into two broad categories: Primitive types and Reference types. Java primitives, for example, are predefined by the Java Virtual machine.

Java reference types fall into four main categories: classes, interfaces, array classes, and generic parameters. Because generic parameters are erased during compilation, the Java virtual machine really only has the first three. Of the classes, interfaces, and array classes, the array class is generated directly by the Java virtual machine, and the other two have corresponding byte streams.

When it comes to byte streams, the most common form is the class file generated by the Java compiler. When Java uses the compiler to compile source files into class files, it verifies them strictly according to the Java Virtual Machine specification, which is described in detail in Chapter 4 of the Java Virtual Machine Specification.

Either a directly generated array class or a non-array class or interface goes through a series of steps to be directly used by the JVM. These steps include data validation, conversion parsing, initialization, and so on. This simple but complex process is called the JVM’s classloading mechanism.

The “classes” in the Class file have seven life cycle phases from loading into JVM memory to unloading out of memory. The class loading mechanism consists of the first five phases.

As shown below:

The start sequence of loading, verification, preparation, initialization and uninstallation is determined. Note that the start sequence is only in order, and the end sequence is not certain. The parsing phase may begin after initialization.

In addition, class loading does not have to wait for “first use” in the program, and it is allowed for the JVM to preload certain classes. (Class loading timing)

loading

When we talk about loading, we don’t mean class loading, but the first step of class loading. Loading is the process of finding byte streams and creating classes based on them. At this stage, the JVM does three things:

Get the binary byte stream (Class file) that defines a Class by its fully qualified name (package name and Class name). The methods can be jar package, WAR package, network access, JSP file generation and so on.

2. Convert the static storage structure represented by this byte stream into the runtime data structure of the method area. This is just transforming the data structure, not merging the data. (The method area is the area of runtime memory used to store loaded class information, constants, static variables, and compiled code.)

Generate an in-memory java.lang.Class object representing the Class as an access point to the Class’s various data in the method area. This Class object is not specified to be in Java heap memory; it is a special object that is stored in the method area.

The array class does not have a byte stream, but is generated directly by the Java virtual machine. For other classes, the Java virtual machine uses the class loader to find the byte stream.

Java virtual machines (VMS) support two types of class loaders: boot class loaders provided by Java VMS and user-defined class loaders. Each user-defined ClassLoader should be an instance of a subclass of the abstract ClassLoader class.

The class loaders provided by the JVM are described as follows:

There are three important classloaders built into the JVM, all of which are implemented by Java and inherit from java.lang.ClassLoader except BootstrapClassLoader:

  • BootstrapClassLoader: the topmost loading class, implemented by C++, that loads jar packages and classes in the %JAVA_HOME%/lib directory or all classes in the path specified by the -xbootclasspath parameter.
  • ExtensionClassLoader: load jar packages and classes in %JRE_HOME%/lib/ext or jar packages in the path specified by the java.ext.dirs system variable.
  • SystemClassLoader(Application class loader) : A loader for our users that loads classes in the application path. (In this case, the application path is the path specified by the vm parameter -cp/-classpath, system variable java.class.path, or environment variable classpath.) By default, classes contained in an application are loaded by the application class loader.

A new concept is involved here — the parental delegation model.

Parental delegation model

The concept is introduced

This hierarchical relationship between class loaders, as shown above, is known as the parent delegate model of class loaders. The parent delegate model requires that all class loaders have their own parent class loaders, except for the top-level start class loaders. The parent-child relationship between class loaders is not typically implemented as an inherited relationship, but rather uses a combinative relationship to duplicate the parent loader’s code.

Each class has a corresponding class loader. Classloders in the system use the parent delegate model by default when working together. During class loading, the system first checks whether the current class has been loaded. Classes that have already been loaded are returned directly, otherwise an attempt will be made to load. When loading, the request is first delegated to the parent class loader’s loadClass(), so all requests should eventually be passed to the top level BootstrapClassLoader. When the parent class loader can’t handle it, you handle it yourself. When the parent class loader is null, the BootstrapClassLoader is used as the parent class loader.

Each class load has a parent class loader, which we verify with the following program.

public class ClassLoaderDemo {
    public static void main(String[] args) {
        System.out.println("ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader());
        System.out.println("The Parent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent());
        System.out.println("The GrandParent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent());
    }
}
Copy the code

The execution result is as follows:

ClassLodarDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
The Parent of ClassLodarDemo's ClassLoader is sun.misc.Launcher$ExtClassLoader@60e53b93
The GrandParent of ClassLodarDemo's ClassLoader is null
Copy the code

The parent class loader of AppClassLoader is ExtClassLoader. The parent class loader of ExtClassLoader is null. Null does not mean that ExtClassLoader does not have a parent class loader, but BootstrapClassLoader.

What are the benefits of the parental delegation model?

For example, the java.lang.Object class in the rt.jar package, no matter which loader loads the class, is ultimately delegated to the top bootstrap class loader, ensuring that the Object class is the same class in all loader environments.

The connection

The loading process of the Class generates the java.lang.Class object of the Class, which then enters the connection phase, which is responsible for merging the binary data of the Class into the JRE (Java runtime environment). There are roughly three phases to join a class.

1. Verification: verify whether the loaded class has the correct structure and whether the class data meets the requirements of the VIRTUAL machine to ensure that the security of the virtual machine will not be harmed. Verification content: file format verification (magic number, version, constant pool….) , metadata validation (semantic validation of bytecode), bytecode validation (method of class), symbol reference validation

2. Prepare: Allocate memory in the method area for static fields of the class and assign default initial values (0 or NULL).

Static int a = 100; static int a = 100; The class variable A is assigned a default value of 0 in the preparation phase.

For general member variables, they are allocated in heap memory along with the object at class instantiation.

In addition, static final filed will assign the initial value set by the program in the preparation stage, such as static final int A = 666; The static constant A is directly assigned to 666 in the preparation phase. For static variables, this is done in the initialization phase.

In addition to allocating memory, some Java virtual machines also construct other data structures related to the class hierarchy at this stage, such as method tables that are used to implement dynamically bound virtual methods.

3. Parse: Replace symbolic references in the binary data of the class with direct references.

What’s the difference between a symbolic reference and a direct reference?

  • Symbolic references use a set of symbols to describe the referenced target, which can be literal constants in any form, defined in the Class file format.
  • A direct reference can be a pointer directly to the target, a relative offset, or a handle that can be indirectly located to the target.

For example, for a method call, the compiler generates a symbolic reference containing the name of the target method’s class, the name of the target method, the type of the received parameter, and the type of the return value to refer to the method being invoked.

The symbolic reference may point to a class that has not been loaded, and parsing will trigger the loading of the class.

Initialize the

The initialization phase is the process of executing the class constructor methods, primarily assigning the initial values set by the static variable program.

If you want to initialize a static field, you can assign it directly at declaration time, or you can assign it in a static code block.

If a directly assigned static field is modified by final and its type is primitive or string, the field is marked by the Java compiler as a ConstantValue, and its initialization is done directly by the Java virtual machine. Any other direct assignment, as well as any code in a static code block, is placed in the same method by the Java compiler and named < Clinit >. The Java virtual machine locks to ensure that the class’s < clinit > methods are executed only once.

The instance instance constructor initializes non-static variable resolution, while the class class constructor initializes static variables, static code blocks.

There are only five cases in which classes must be initialized in a Java virtual machine.

  • Execute new, getStatic (read static fields), putStatic (set static field values), and Invokestatic (invoke a static method) directives;

  • Use reflect to make a reflection call to a class;

  • When a class is initialized, the parent class is initialized beforehand.

  • To start a VM, initialize the class containing the main method.

  • In JDK1.7, if Java. Lang. Invoke the final analytical results REF_getStatic MethodHandler instance, REF_putStatic, REF_invokeStatic method handles, And the class corresponding to the method handle is not initialized;

Class initialization is not triggered in the following cases

A reference to a static field by a subclass triggers initialization of the parent class, not the subclass.

class Parent {
    static int a = 100;
    static {
        System.out.println("The parent init!"); }}class Child extends Parent {
    static {
        System.out.println("The child init!"); }}public class Init{  
    public static void main(String[] args){ System.out.println(Child.a); }}// Output the resultThe parent init!100
Copy the code

2. Define an array of objects without triggering initialization of the class.

public class Init{  
    public static void main(String[] args){  
        Parent[] parents = new Parent[10]; }}// No output
Copy the code

3. Constants are stored in the constant pool of the calling class at compile time. In essence, there is no direct reference to the class in which the constant is defined.

class Const {
    static final int A = 100;
    static {
        System.out.println("Const init"); }}public class Init{  
    public static void main(String[] args){ System.out.println(Const.A); }}/ / output
100
Copy the code

4. Fetching a Class object by its name does not trigger Class initialization.

public class test {
   public static void main(String[] args) throws ClassNotFoundException {
        Class c_dog = Dog.class;
        Class clazz = Class.forName("zzzzzz.Cat"); }}class Cat {
    private String name;
    private int age;
    static {
        System.out.println("Cat is load"); }}class Dog {
    private String name;
    private int age;
    static {
        System.out.println("Dog is load"); }}/ / output
Cat is load
Copy the code

If initialize is false, Class initialization is not triggered. This parameter tells the vm whether to initialize the Class.

Class clazz = Class.forName("com.msdn.domain.Cat", false, Cat.class.getClassLoader());
Copy the code

The default loadClass method of ClassLoader does not trigger the initialization action.

new ClassLoader(){}.loadClass("com.msdn.domain.Cat");
Copy the code

extension

We’ve all touched on the singleton pattern before, so let’s look at an example of lazy singleton initialization:

public class Singleton { private Singleton() {} private static class LazyHolder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return LazyHolder.INSTANCE; }}Copy the code

Only when singleton.getInstance is called will lazyholder.instance be accessed, initialization of LazyHolder (corresponding to executing invokeStatic instruction) be triggered, and a new INSTANCE of Singleton be created.

The principle behind static inner classes implementing the singleton pattern is described above: lazy-loaded inner classes are implemented through the JVM’s feature that class initialization is thread-safe. Classes are first loaded into the JVM, but only initialized for actual use. This initialization is implemented by the JVM itself and is thread-safe, so programs can ensure that there is only one Singleton instance in a multithreaded environment.

Here’s another example:

public class Singleton {

  private Singleton(a) {}private static class LazyHolder {
    static final Singleton INSTANCE = new Singleton();
    static {
      System.out.println("LazyHolder.<clinit>"); }}public static Object getInstance(boolean flag) {
    if (flag) {
      return new LazyHolder[2];
    }
    return LazyHolder.INSTANCE;
  }

  public static void main(String[] args) {
    getInstance(true);
    System.out.println("--");
    getInstance(false); }}Copy the code

The execution result is as follows:

----
LazyHolder.<clinit>
Copy the code

Conclusion: If flag is true, a new array is created. Executing this statement only loads the LazyHolder, but does not initialize the class. The LazyHolder is actually connected and initialized on getInstance(False)

You can run the following command to view the difference:

$ javac Singleton.java
$ java -verbose:class Singleton
Copy the code

reference

The JVM class loading process

JVM class loading stuff

Java Virtual Machine Specification