Class loaders? This one is enough

thinking

How does the code or program we write actually work? For example, I use the Java language development, the source code is.java files, but they are no way to run. We usually package them into jars and deploy them to the server. What we mean by packaging is compilation, that is, compiling Java files into.class bytecode files. How do we execute those.class bytecode files? These.class files are executed using the java-jar command. The java-jar command actually starts a JVM process that runs the bytecode files

How does the JVM load these class files?

Above we said that the JVM runs.class bytecode files, but how do they get loaded in?

Through the classloader, of course, which loads the.class file

Load -> Verify -> Prepare -> Parse -> initialize

Let’s take a look at the whole process of loading, but before we look at the whole process, let’s look at the conditions of class loading

Class loading condition

There are many class files in a program. Does the JVM load these files unconditionally?

The JVM only loads the class file when it is “used” by the JVM. Active use of the class file only occurs when:

1. When creating an instance of a class, such as using the new keyword or reflection, clone, deserialization

2. The bytecode Invodestatic directive is used when invoking static methods of a class

3. When using static fields of a class or interface (except final constants), such as getStatic or putStatic directives

4. When a class’s methods are reflected using methods in the java.lang.Reflect package

5. When initializing a child class, initialize the parent class first

6. The class that contains the main() method to start the virtual machine

Except the 6 points listed above are active use, the others are passive use

Examples of active use

public class Parent {
    static {
        System.out.println("Parent init"); }}public class Child extends Parent {
    static {
        System.out.println("Child init"); }}public class Main {
    public static void main(String[] args) {
        Child child = newChild(); }}Copy the code

If the Parent class is initialized, “Parent init” is printed. If the Child class is initialized, “Child init” is printed. Initialize the Child class by executing the Main method of the Main class.

Parent init Child init

By printing the results, we can verify that two conditions, 1 and 5, are true for active use of class files

I won’t give you any examples of active use, but let’s look at passive use

Examples of passive use

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

Add a static variable v to the Parent class, but not to the Child class, and then access child. v from the Main class. Will the Child class be loaded?

The following output is displayed:

Parent init 60

The Parent class has been loaded, but the Child class has not been loaded. The Parent class has been loaded. This can be verified by adding the -xx :TraceClassLoading parameter), but is not initialized.

-xx: Output after TraceClassLoading

[Loaded jvm.loadclass.Parent from file:/D:/workspace/study/study_demo/target/classes/] [Loaded jvm.loadclass.Child from file:/D:/workspace/study/study_demo/target/classes/] Parent init 60

So when a field is used, only the class that directly defines the field is initialized

In the third point of active use, it is clear that using the final constant of a class is not active use, so the corresponding class will not be loaded. Let’s verify this by the code

public class ConstantClass {
    public static final String CONSTANT = "constant";
    static {
        System.out.println("ConstantClass init"); }}public class Main {
    public static void main(String[] args) { System.out.println(ConstantClass.CONSTANT); }}Copy the code

The following output is displayed:

[Loaded jvm.loadclass.Main from file:/D:/workspace/study/study_demo/target/classes/]

constant

The result does verify that final constants do not cause class initialization because constant values are optimized at compile time (the technical name is “constant propagation optimization “) by putting the constant value “constant” directly into the constant pool of the Main class, so the ConstantClass class is not loaded

loading

Load is the first phase of the class loading process, and during load, the JVM needs to do the following:

1. Get the binary data stream of the class by its fully qualified class name

2. Parse the binary data stream of the class into the data structure in the method area

3. Create an instance of the java.lang.Class Class representing this type

There are many ways to get binary streams of a class, such as reading them directly into a.class file, or extracting.class files from archive packages such as JARS, zip, and WAR. The JVM then processes these binary streams and generates an instance of java.lang.class. This instance is the interface to access type metadata, which is key data for implementing reflection

validation

The verification stage is to ensure that the loaded bytecode conforms to the JVM specification, which is generally divided into format check, semantic check, bytecode verification, symbol reference verification, as follows:

To prepare

The preparation phase is mainly to allocate the corresponding memory space for the class and set the initial value. The commonly used initial value is shown in the following table:

The data type Default initial value
int 0
long 0L
short (short)0
char ‘\u0000’
boolean fasle
float 0.0 f
double 0.0 d
reference null

If a constant is defined in a class, for example:

public static final String CONSTANT = "constant";
Copy the code

This constant (see the bytecode file with the ConstantValue property) is stored directly in the constant pool during the preparation phase

 public static final java.lang.String CONSTANT;
    descriptor: Ljava/lang/String;
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String constant
Copy the code

parsing

The parsing phase mainly converts symbolic references to classes, interfaces, fields, and methods into direct references

Symbolic reference: A symbolic reference is a set of symbols that describe the referenced target. The symbol can be any literal, as long as it is used unambiguously to locate the target

Direct reference: A direct reference is a pointer that points directly to the target, a relative offset, or a handle that can be indirectly located to the target

The parsing phase focuses on classes or interfaces, fields, class methods, interface methods, method types, method handles, and call point qualifiers

Let’s use an example to explain this briefly

public class Demo {
    public static void main(String[] args) { System.out.println(); }}Copy the code

View the bytecode corresponding to the system.out.println () method in the main method

3: invokevirtual #3                  // Method java/io/PrintStream.println:()V
Copy the code

If constant pool item 3 is used, let’s look at constant pool item 3 as follows:

#3 = Methodref          #17.#18        // java/io/PrintStream.println:()V
Copy the code

It appears that we need to continue to find the reference relationship, items 17 and 18, as follows:

#17 = Class              #24            // java/io/PrintStream
#18 = NameAndType        #25:#7         // println:()V
Copy the code

Item 17 refers to item 24, and item 18 refers to item 25 and 7, respectively:

#24 = Utf8               java/io/PrintStream
#25 = Utf8               println
#7 = Utf8               ()V
Copy the code

We represent the above reference relationships in a diagram as follows:

In fact, the above reference relationship is symbolic reference

But when the program is run by the symbolic reference is not enough, the system needs to know the method of position, so the JVM prepared a method table for each class, the all of the methods are listed in the table, when need to call a class method, as long as know this method in the offset can be called directly in the table. By parsing, symbolic references can be converted to the position of the target method in the class method table, resulting in a successful invocation of the method.

Initialize the

Initialization is the last stage of class loading, and as long as the previous stages are ok, the initialization stage is entered. So what does the initialization phase do?

The main thing is to execute the class’s initialization method (which is automatically generated by the compiler), which is generated by both the assignment statement of the class’s static member variables and the static statement block. This is the stage where the actual assignment is performed. The preparation phase simply allocates the appropriate memory space and sets the initial values.

Let’s verify this with a small example

public class StaticParent {
    public static int id = 1;
    public static int num ;
    static {
        num = 4; }}Copy the code

The corresponding part of the bytecode file is as follows:

#13 = Utf8               <clinit>
static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_1
         1: putstatic     #2                  // Field id:I
         4: iconst_4
         5: putstatic     #3                  // Field num:I
         8: return

Copy the code

You can see that in the method, the static variable ID in the class and num in the static statement block are assigned

Does the compiler generate methods for all classes? The answer is no, if a class has neither assignment nor static block, then there is nothing to do even if the method is generated, so the compiler does not insert it. Let’s look at the corresponding bytecode through an example

public class StaticFinalParent {
    public static final int a = 1;
    public static final int b = 2;
}
Copy the code
public jvm.loadclass.StaticFinalParent();
    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

Copy the code

There are no methods found in the bytecode because, as we said earlier, constants of final type are initialized during the preparation phase, so they don’t need to be initialized during initialization.

Pay attention to the point

One important thing to note here is that the JVM guarantees the safety of the method because there may be multiple threads initializing the class at the same time, so that only one thread can execute the method while the other threads wait. Once one thread successfully initializes the class, the other threads do not need to initialize the method again

A small summary

The Java files we write are compiled into class bytecode files, and the JVM loads the active classes into memory and starts executing the programs. An important stage is the loading of classes, the binary stream of class files from the external system, and the defining stage is the class loader described below

Class loader

ClassLoader represents the class loader, which is the core component of Java. It can be said that all class files are read into the system from the outside by the ClassLoader, and then handed over to the JVM for subsequent connection, initialization and other operations.

classification

The JVM creates three types of classloaders: startup classloaders, extension classloaders, and application classloaders. Let’s take a brief look at each class loader

Start the class loader

Bootstrap ClassLoader is mainly responsible for loading the core classes of the system, such as the Java classes in rt.jar. When you use Java in Linux or Windows, you will install JDK. In fact, the lib directory contains these core classes

Extend the class loader

The Extension ClassLoader is mainly used to load Java classes in lib\ext, which will support system running

Apply the class loader

Application ClassLoader mainly loads user classes, that is, libraries specified on the user ClassPath, which are usually written by ourselves

Parental delegation model

When a class is loaded, the system determines whether the current class is loaded. If it is loaded, it returns the available class directly, otherwise it tries to load the class. When a class is attempted to load, it is delegated to its parent loader and eventually passed to the top-level loader. If the parent class loader does not find the class in its area of responsibility, it pushes it down to the subclass loader. The loading is as follows:

The delegate process to check whether the class is loaded is one-way. The underlying class loader asks for a long time, but at the end of the day, it still loads the class itself. This has its advantages, of course, because it provides structural clarity and, most importantly, prevents multiple hierarchies of loaders from reloading certain classes

Disadvantages of the parent delegation model

The parent delegate model checks that class loading is one-way, but the downside is that the upper class loader cannot access classes loaded by the lower class loader. If the system class loaded by the bootstrap class loader provides an interface that needs to be implemented in the application, it also binds a factory method that creates an instance of that interface. The interface and factory methods are in the boot class loader. The problem arises when the factory cannot create an application instance loaded by the application class loader. JDBC, XML Parser, etc

The JVM is so powerful that there must be a way to solve this problem. Yes, the Service Provider Interface (SPI) mechanism in Java can solve this problem

conclusion

This article mainly introduces the class loading mechanism of JVM, including the whole process of class loading and some of the things that are done at each stage. Then the working mechanism of class loader and parental delegation model are introduced. More input knowledge, I hope you continue to research, such as OSGI mechanism, hot replacement and hot deployment implementation, etc

The resources

1. Real Java Virtual Machine

2. Deep Understanding of the Java Virtual Machine

3. “From 0 to take you to become a JVM combat master”, public account reply “JVM” can check the information

Welcome to pay attention to the public number [every day white teeth], get the latest article, we communicate together, common progress!