Small knowledge, big challenge! This paper is participating in theEssentials for programmers”Creative activities

This article also participated in the “Digitalstar Project” to win a creative gift package and creative incentive money

Operating environment:

Here’s my running environment. I’m doing it on a MAC. First find the Java address of the MAC. You can see this in ~/.bash_profile

Java home directory: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/home


I. The process of class loading

1.1 Class loader initialization process

Suppose you now have a Java class com.lxl.jvm.Math with a main method in it

package com.lxl.jvm; public class Math { public static int initData = 666; public static User user = new User(); public int compute() { int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math = new Math(); math.compute(); }}Copy the code

This method is very simple, usually we just execute main and we’re ready to run the program, so how does the whole process load and run when we hit Main? Why do I get results when I hit Main?

First, let’s take a look at the class loading process (macro process), as shown below:

Remark:

1. The Java launcher is java.exe on Windows and Java on MAC

2. In the PART of C language, we will learn about it, and in the Part of Java, we need to master it.

Step 1: Java calls the underlying jvm.dll file to create a Java virtual machine (this step is implemented by C++). Exe is written by c++ code, and the JVM. DLL is also a c++ low-level function. A Java virtual machine is created by calling the JVM.dll file, which is the equivalent of a Java JAR package.

Step 2: During the startup of the virtual machine, an instance of the boot class loader is created. The loader for this bootstrap class is implemented in C. Then the JVM virtual machine starts up.

Step 3: next, the C++ language calls the Java launcher. I just created the Java Virtual machine, and the Java Virtual machine has a lot of launchers in it. One program is called Launcher. The class stands for Sun.misc.launcher. By starting this Java class, the class boot loader loads and creates many other class loaders. These loaders are the ones that actually start and load the bytecode files on disk.

Step 4: Actually load the bytecode file on the local disk, and then execute the main method (this step will explain how to load the bytecode file on the local disk).

Step 5: after the main method completes, the boot classloader issues a c++ call that destroys the JVM

That’s all it takes to start a main method, the class load

How did our com.lxl.Math class get loaded into the Java virtual machine?

1.2 The process of class loading

The com.lxl.jvm.Math class above eventually generates clAS bytecode files. How do bytecode files get loaded into the JVM by the loader?

There are five steps to class loading: load, validate, prepare, parse, and initialize. So what are these five steps? Let’s see

Where is our class? On disk (such as the class file in the Target folder), we first load the class class into memory. After loading it into memory, instead of simply converting it into a binary bytecode file, it goes through a series of processes. For example: validation, preparation, parsing, initialization, etc. Convert these columns of information into meta information and put it in memory. Let’s look at the process

Step 1: Load.

Loading the Class into the Memory of the Java virtual machine takes a series of operations before loading it into memory. The first step is to validate the bytecode.

Step 2: Verify

Verify that bytecode loads correctly, for example by opening a bytecode file. At first glance, it looks like gibberish, but it’s not. In fact, each of these strings has a meaning. So can we replace the contents in the file? Of course not. Once you replace it, you can’t do it. So, step one: Verify, verify what?

Verify that the bytecode load is correct: The format is correct. Check whether the content complies with Java VIRTUAL machine specifications.

Step 3: Prepare

After verification, it’s time to prepare. What are you going to do? Our Math class, for example, first assigns an initial value to a static variable in Math. Let’s say we have two static variables in Math

public static int initData = 666;
public static User user = new User();
Copy the code

During preparation, both variables are assigned an initial value that is not a real value, such as initData with an initial value of 0. If it is Boolean, the value is false. That is, the value assigned during the preparation phase is fixed to the JVM, not the value defined by us. If a final constant, such as public static final int name=”zhangsan”, then it is initialized directly with the initial value “zhangsan”. This is just assigning an initial value to a static variable

Step 4: Parse

Let’s talk about the parsing process. Parsing is a slightly more complicated process, transforming a “symbolic reference” into a direct reference.

What is a symbolic reference?

For example, the main method in our program. The notation is fixed, so we can think of main as a symbol. For example, initData, int, static, we can call them symbols, Java virtual machine has a technical term, they are called symbols. These symbols are loaded into memory with an address. Converting symbolic references to direct references means converting main, initData, int, and so on to the corresponding memory address. This address is a direct reference to the code. From the value of the direct reference, we can know where the code is. And then get the code and actually run it.

Converting symbolic references to “memory addresses” is called static linking. The above parsing process is equivalent to the static chaining process. During class loading, the conversion of symbols to memory addresses is completed. There are static links, and then there are dynamic links.

What is dynamic linking?

public static void main(String[] args) {
        Math math = new Math();
        math.compute();
    }
Copy the code

For example, in this code, I only go back to loading compute() when I get to math.pute (). That is, I don’t necessarily parse the compute() method into memory addresses at load time. It only resolves when it gets to this row.

Let’s look at assembly code

javap -v Math.class
Copy the code
Classfile /Users/luoxiaoli/Downloads/workspace/project-all/target/classes/com/lxl/jvm/Math.class Last modified The 2020-6-27; size 777 bytes MD5 checksum a6834302dc2bf4e93011df4c0b774158 Compiled from "Math.java" public class com.lxl.jvm.Math minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #9.#35 // java/lang/Object."<init>":()V #2 = Class #36 // com/lxl/jvm/Math #3 = Methodref #2.#35 // com/lxl/jvm/Math."<init>":()V #4 = Methodref #2.#37 // com/lxl/jvm/Math.compute:()I #5 = Fieldref #2.#38 // com/lxl/jvm/Math.initData:I #6 = Class #39 // com/lxl/jvm/User #7 = Methodref #6.#35 // com/lxl/jvm/User."<init>":()V #8  = Fieldref #2.#40 // com/lxl/jvm/Math.user:Lcom/lxl/jvm/User; #9 = Class #41 // java/lang/Object #10 = Utf8 initData #11 = Utf8 I #12 = Utf8 user #13 = Utf8 Lcom/lxl/jvm/User; #14 = Utf8 <init> #15 = Utf8 ()V #16 = Utf8 Code #17 = Utf8 LineNumberTable #18 = Utf8 LocalVariableTable #19 = Utf8 this #20 = Utf8 Lcom/lxl/jvm/Math; #21 = Utf8 compute #22 = Utf8 ()I #23 = Utf8 a #24 = Utf8 b #25 = Utf8 c #26 = Utf8 main #27 = Utf8 ([Ljava/lang/String; )V #28 = Utf8 args #29 = Utf8 [Ljava/lang/String;  #30 = Utf8 math #31 = Utf8 MethodParameters #32 = Utf8 <clinit> #33 = Utf8 SourceFile #34 = Utf8 Math.java #35 = NameAndType #14:#15 // "<init>":()V #36 = Utf8 com/lxl/jvm/Math #37 = NameAndType #21:#22 // compute:()I #38 = NameAndType #10:#11 // initData:I #39 = Utf8 com/lxl/jvm/User #40 = NameAndType #12:#13 // user:Lcom/lxl/jvm/User;  #41 = Utf8 java/lang/Object { public static int initData; descriptor: I flags: ACC_PUBLIC, ACC_STATIC public static com.lxl.jvm.User user; descriptor: Lcom/lxl/jvm/User; flags: ACC_PUBLIC, ACC_STATIC public com.lxl.jvm.Math(); descriptor: ()V flags: 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 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/lxl/jvm/Math; public int compute(); descriptor: ()I flags: ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: bipush 10 9: imul 10: istore_3 11: iload_3 12: ireturn LineNumberTable: line 8: 0 line 9: 2 line 10: 4 line 11: 11 LocalVariableTable: Start Length Slot Name Signature 0 13 0 this Lcom/lxl/jvm/Math;  2 11 1 a I 4 9 2 b I 11 2 3 c I public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class com/lxl/jvm/Math 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokevirtual #4 // Method compute:()I 12: pop 13: return LineNumberTable: line 15: 0 line 16: 8 line 17: 13 LocalVariableTable: Start Length Slot Name Signature 0 14 0 args [Ljava/lang/String; 8 6 1 math Lcom/lxl/jvm/Math; MethodParameters: Name Flags args static {}; descriptor: ()V flags: ACC_STATIC Code: stack=2, locals=0, args_size=0 0: sipush 666 3: putstatic #5 // Field initData:I 6: new #6 // class com/lxl/jvm/User 9: dup 10: invokespecial #7 // Method com/lxl/jvm/User."<init>":()V 13: putstatic #8 // Field user:Lcom/lxl/jvm/User; 16: return LineNumberTable: line 4: 0 line 5: 6 } SourceFile: "Math.java"Copy the code

Using this directive, you can view the Math binaries. In fact, this file is the binary file above.

See what’s in here?

Class name, size, modification time, large version, small version, access modifiers, and so on

 Last modified 2020-6-27; size 777 bytes
  MD5 checksum a6834302dc2bf4e93011df4c0b774158
  Compiled from "Math.java"
public class com.lxl.jvm.Math
  minor version: 0
  major version: 52
Copy the code

There is also a Constant pool. There are a lot of things in this constant pool. Let’s focus on the middle line. The first column represents a constant identifier that may be used elsewhere. The second column represents constant content.

Constant pool: #1 = Methodref #9.#35 // java/lang/Object."<init>":()V #2 = Class #36 // com/lxl/jvm/Math #3 = Methodref #2.#35 // com/lxl/jvm/Math."<init>":()V #4 = Methodref #2.#37 // com/lxl/jvm/Math.compute:()I #5 = Fieldref #2.#38 // com/lxl/jvm/Math.initData:I #6 = Class #39 // com/lxl/jvm/User #7 = Methodref #6.#35 // com/lxl/jvm/User."<init>":()V #8  = Fieldref #2.#40 // com/lxl/jvm/Math.user:Lcom/lxl/jvm/User; #9 = Class #41 // java/lang/Object #10 = Utf8 initData #11 = Utf8 I #12 = Utf8 user #13 = Utf8 Lcom/lxl/jvm/User; #14 = Utf8 <init> #15 = Utf8 ()V #16 = Utf8 Code #17 = Utf8 LineNumberTable #18 = Utf8 LocalVariableTable #19 = Utf8 this #20 = Utf8 Lcom/lxl/jvm/Math; #21 = Utf8 computeCopy the code

These identifiers will be used later, as in the main method

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class com/lxl/jvm/Math
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method compute:()I
        12: pop
        13: return
      LineNumberTable:
        line 15: 0
        line 16: 8
        line 17: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  args   [Ljava/lang/String;
            8       6     1  math   Lcom/lxl/jvm/Math;
    MethodParameters:
      Name                           Flags
      args
Copy the code

That’s where #2, #3, and #4 are all references to identifiers.

A new Math() is created.

         0: new           #2                  // class com/lxl/jvm/Math
Copy the code

What is new + #2. #2? If you look in the constant pool, #2 represents the Math class

   #2 = Class              #36            // com/lxl/jvm/Math
Copy the code

The math.pute () method is not loaded into memory when the class is loaded, but when the main method is run, it is loaded until this line of code is executed. This process is called dynamic linking.

When a class is loaded, we can think of “parsing” as static loading. Normally like static methods (such as the main method), fetching other static methods that do not change will be loaded directly into memory, because for performance reasons, they will not change after loading, and will be directly converted to the code location in memory.

For methods like math.pute (), which may change during loading (compute is polymorphic, with multiple implementations), we don’t know who will be called at initial loading until runtime. So at runtime, it dynamically queries its location in memory, a process called dynamic loading

Step 5: Initialize

A static variable of a class is initialized to the specified value. Execute a static code block. Such as code

public static int initData = 666;
Copy the code

It is assigned a value of 0 during the preparation phase, and a set value of 666 during initialization

1.3 lazy loading of classes

After a class is loaded into the method area, it mainly contains: runtime constant pool, type information, field information, method information, class loader reference, reference to the corresponding class instance and so on.

What does that mean? That is, when a class is loaded into memory, the constants of that class, have constant names, type, field information, etc.; Symbolic information about methods such as method names, return value types, parameter types, and method scopes is loaded into different areas.

Note: If the main class uses other classes at run time, these classes are progressively loaded, that is, lazily loaded. Load only when you need it.

package com.lxl.jvm; public class TestDynamicLoad { static { System.out.println("********Dynamic load class**************"); } public static void main(String[] args) { new A(); System.out.println("*********load test*****************"); B b = null; // b will not be loaded unless new b (); } } class A { static { System.out.println("********load A**************"); } public A(){ System.out.println("********initial A**************"); } } class B { static { System.out.println("********load B**************"); } public B(){ System.out.println("********initial B**************"); }}Copy the code

There are two classes, A and B, which are loaded when used. For example, in the main method, B is not used, so it is not loaded into memory.

The results

********Dynamic load class**************
********load A**************
********initial A**************
*********load test*****************
Copy the code

We see that class A is loaded, but class B is not. The reason is that class B is declared, not used.

The following points are summarized:

  1. Static code blocks are executed before constructors
  1. Classes that are not actually used are not loaded

Class loaders

2.1 Types of class loaders

Classes are loaded mainly through class loaders, and Java has several types of loaders

1. Bootstrap ClassLoader

In the class loading flow above, what is the purpose of the boot class loader when an instance of the boot class loader is created during virtual machine startup? Load class

The boot class loader is responsible for loading the most core Java types. These libraries are located in the lib directory of the JRE directory **. For example :rt.jar, charset.jar, etc.

2. Ext ClassLoader

The extension class loader is primarily used to load jar packages for extensions. The jar package is located in the lib/ext extension of the JRE directory

3. App CloassLoader

It is mainly used to load classes written by users themselves. Responsible for loading class packages in the classPath path

4. Custom class loaders

Load the class package in the user-defined path

The bootstrap class loader is implemented by C++, which then constructs the ExtClassLoader and AppClassLoader from a Launcher class and constructs the relationship between them.

2.2 case

Case 1: Test the JDK’s built-in classloader

package com.lxl.jvm; import sun.misc.Launcher; import java.net.URL; Public class TestJDKClassLoader {public static void main(String[] args) {/** * String is the JDK's own class, located in the jre/lib core directory, so its classloader is the boot classloader. Is our current definition of class, will be the application class loader loads * / System. Out. The println (String) class) getClassLoader ()); System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName()); }}Copy the code

Let’s look at this simple code and see the result:

Null sun.misc.Launcher$ExtClassLoader Sun.misc.Launcher$AppClassLoader String is the JDK's own class, so its class loader is the boot class loader, the boot class loader is c++ code, so null second: The encryption class classloader, which is a class in the JDK extension package, is used in the extClassLoader classloader load third: is our current definition of the class, will be loaded by the AppClassLoader application.Copy the code

We see that ExtClassLoader and AppClassLoader are both part of the Launcher class. What is the Launcher class?

As mentioned above, the Launcher class is a class that is launched by a C++ call when the JVM starts. This class bootloader loads and creates other class loaders.

So, why did the first bootstrap class loader return null?

Since bootstrap bootstraps the classloader, it is not a Java object, it is a c++ generated object, so it is not visible here

Case 2: Relationship between BootstrapClassLoad and ExtClassLoader and AppClassLoader

As shown above, the C program code implementation is on the left and the Java code implementation is on the right. Here is cross-language invocation, JNI implements cross-language invocation of c++ to Java. The first Java class that C calls is the Launcher class.

As you can see from this figure,C++ calls Java to create JVM initiators. One of those initiators is the Launcher, which actually calls the getLauncher() method of the sun.misc.launcher class. So let’s start with this method and see how it works, okay?

We can see that the Lanucher. Java class is in the core rt.jar package. Lanucher is a very core class.

We see that the getLauncher() class returns directly to the launcher. The launcher is a static object variable, which is a singleton pattern

C++ calls getLauncher()–> directly returns the lanucher object, which is initialized when the class is built. So, what do we do when we initialize? Now let’s look at his construction.

In the constructor, we first define an ExtClassLoader. This is an extension classloader that calls getExtClassLoader(). So what does getExtClassLoader do?

This is a typical way to write multithreaded synchronization.

If not, create an ExtClassLoader() object and see what createExtClassLoader() does.

Return new Launcher.ExtClassLoader(var1). New an ExtClassLoader with the argument var1, which represents a file in the ext extension directory.

In the ExtClassLoader(File[] var1) method, the first step is to call the super constructor of the parent class. And who does ExtClassLoader inherit from? We can see that it inherits the URLClassLoader.

What does the URLClassLoader do? Actually, if you think about it, you can probably guess, there are some file paths, file paths to load the class class.

Continuing with the super(parent) call, we continue down to see that the constructor of the ClassLoader interface is called:

Who is the parent of the ExtClassLoader? Notice that the parent class of ExtClassLoader is null.

This is the parent classloader that was passed in. Why is the parent classloader null? Because who is the parent class loader of an ExtClassLoader? Bootstrap ClassLoader is a C++ ClassLoader. We cannot call it directly, so set it to null.

In fact, the ExtClassLoader initializes the ExtClassLoader class by calling the ExtClassLoader method in the initialization phase

Now, let’s go back to the constructor of the Launcher and see what the Launcher does next.

As you can see, the AppClassLoader method getAppClassLoader(var1) is called next. The thing to notice is the parameter var1. Who is var1? Looking up, you can see that VAR1 is an ExtClassLoader.

This is the AppClassLoader, the application class loader, and this class is the classloader that loads our own defined classes. It also inherits from URLClassLoader.

Let’s look at the getAppClassLoader(Final ClassLoader var0) method. The arguments to this method are the ExtClassLoader passed above

The first sentence here is to get the current project’s class file path and convert it to a URL. AppClassLoader(var1x, var0), where var1x is the path of the class class and var0 is the ExtClassLoader of the extended class

AppClassLoader directly calls the constructor of its parent class with arguments to the class classpath collection and ExtClassLoader

Finally, we see that the ExtClassLoader is passed to the parent variable. This is a property defined in ClassLoader, which is the parent class of all classloaders. Therefore, we can also see that the parent class loader of AppClassLoader is ExtClassLoader

At the same time, we see that C++ calls the Launcher class when starting the JVM, which loads both ExtClassLoader and AppClassLoader.

Public static void main (String [] args) {this appClassLoader. = this getSystemClassLoader (); This extClassLoader = appClassLoader. GetParent (); This bootstrapClassLoad = extClassLoader. GetParent (); System.out.println("bootstrap class loader: "+ bootstrapClassLoad); System.out.println("ext class loader "+ extClassLoader); System.out.println("app class loader "+ appClassLoader); }Copy the code

The parent class of the appClassLoader is extClassLoader and the parent class of the extClassLoader is bootstrapClassLoader

Output result:

bootstrap class loader: null
ext class loader sun.misc.Launcher$ExtClassLoader@2a84aee7
app class loader sun.misc.Launcher$AppClassLoader@18b4aac2 
Copy the code

Through the source code analysis above, we find that the boot class loader creates and loads extension class loaders and application class loaders. The parent of the extension class loader is the boot class loader. The parent of the application class loader is the extension class loader. This structure determines how the following classes are loaded, namely the parent delegate mechanism.