Chapter 2, JVM classloading mechanism — classloader subsystem

Reference Book: Understanding the Java Virtual Machine in Depth

Reference links:

  • Is still silicon valley: www.bilibili.com/video/BV1PJ…
  • Refer to link 1: Loading processes such as Deep Understanding Java Virtual Machine Notes 06
  • Reference link 2: gitee.com/moxi159753/…

1. General architecture diagram of JVM

Knowing the overall structure of the JVM, what are the main structures to consider if you want to write a Java virtual machine by hand?

  • Class loader
  • Execution engine [interprets instructions in bytecode line by line]

So these two parts are the most important in the JVM

Key contents of this section:

2. Overview of the class loader subsystem

2.1, the introduction

In Depth understanding the Java Virtual Machine

The information described in the Class file ultimately needs to be loaded into the virtual machine before it can be run and used. How does the virtual machine load these Class files? What happens to the information in the Class file when it enters the virtual machine? This is what we need to know in this section

The class loading mechanism of a VM:

The Java VIRTUAL machine loads the data describing the Class from the Class file into memory, verifies, converts, and initializes the data, and finally forms Java types that can be directly used by the virtual machine. Unlike languages where wiring is done at compile time, the Java language loads, joins, and initializes types at runtime.

This strategy, while imposing a slight performance overhead on class loading, provides a high degree of flexibility for Java applications, where dynamically extensible language features rely on runtime dynamic loading and dynamic wiring.

Convention:

  • In practice, each Class file has the potential to represent a Class or interface in the Java language.
  • “Class file” does not refer specifically to a file that exists on a specific disk. It should refer to a string of binary byte streams in any form.

2.2. Class loading subsystem

  1. The classloader subsystem is responsible for loading Class files from the file system or network. Class files have a special file at the beginning of the file identifying the magic number CAFEBABE.

  2. The ClassLoader is only responsible for loading the class file, and whether it can run is determined by the Execution Engine

  3. The Class loader basically loads a bytecode file into memory, generating a large Class instance. The Class information in the loaded Class file is stored in a piece of memory called the method area. In addition to Class information, the method area also stores runtime Constant pool information, possibly including string literals and numeric constants (this part of the Constant pool information is a memory map of the Constant pool part of the Class file).

Constant pool:
   #1 = Methodref          #3.#21         // java/lang/Object."<init>":()V
   #2 = Class              #22            // com/atguigu/java/StackStruTest
   #3 = Class              #23            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
Copy the code

Example: Illustrates the role of the ClassLoader, as shown below

  1. A class file exists on the local hard disk and can be interpreted as a template drawn on paper by the designer, which is eventually loaded into JVM memory to instantiate n identical instances from this file.
  2. The class file is loaded into the JVM, called the DNA metadata template, and placed in the method area.
  3. You can use the getClassLoader method to know which class loader loaded the class

So review: In the. Class file –>JVM– > eventually become the metadata template, the successful implementation of this process requires a transport (the class Loader), It acts as a Courier within the classloader subsystem.

3. Class loading process

3.1 overview,

Class loader subsystem:

For example, the following simple code, its entire class loading process, execution process is how it?

public class HelloLoader {
    public static void main(String[] args) {
        System.out.println("I'm already loaded."); }}Copy the code

As shown below: The first step is to use the ClassLoader to load the bytecode file HelloLoader. The first step is to use the ClassLoader to load the bytecode file HelloLoader. The first step is to use the ClassLoader to load the bytecode file HelloLoader. To execute the code inside. An exception is thrown if the bytecode file fails to load because it is illegal.

3.2. Attention points [Extension]

  • Load -> Verify -> Prepare -> initialize -> Unload There is a definite order to the five stages, and the loading process of the class must begin in this order

  • The parsing phase is not necessarily the case: it can in some cases start after the initialization phase, in order to support runtime binding (also known as dynamic binding or late binding) in the Java language.

  • The virtual machine specification does not enforce restrictions on when to start the first phase of the class loading process, “loading”

  • For the initialization phase, the virtual machine specification strictly specifies five cases in which classes must be “initialized” immediately (while loading, validation, and preparation naturally need to begin before then) :

    • You encounter new, getstatic, putstatic, or InvokestaticBytecode instruction, if the class is not initialized, its initialization phase needs to be triggered first. Typical Java code scenarios that can generate these four instructions are:
      • New: when an object is instantiated using the new keyword
      • Putstatic: Sets static fields of a class (except static fields that are final and have been put into the constant pool by the compiler)
      • Getstatic: Reads static fields of a class (except static fields that are final and have been put into the constant pool by the compiler)
      • Invokestatic: When a static method of a class is invoked
    • When a reflection call is made to a class using the java.lang.Reflect package’s methods, initialization needs to be triggered if the class has not already been initialized.
    • When initializing a class, if the parent class has not been initialized, the initialization of the parent class must be triggered first.
    • When the virtual machine starts, the user needs to specify a main class to execute (the one containing the main() method), which the virtual machine initializes first.
    • When using JDK1.7 dynamic language support, if a Java lang. Invoke. The final analytical results REF_getStatic MethodHandle instance, REF_putStatic, REF_invokeStatic method handles, If the class to which the method handle corresponds has not been initialized, initialization must be triggered first.

The action of having and only having these five scenarios is called making an active reference to a class. In addition, all ways of referring to a class do not trigger initialization, called passive references.

An example of passive quoting:

package org.fenixsoft.classloading;

/** * Passive use of class fields demo 1: * Referencing a static field of a parent class by a subclass does not cause the subclass to initialize */
class SuperClass{
    static{
        System.out.println("SuperClass init!");
    }
    public static int value = 123;

}

class SubClass extends SuperClass{
    static{
        System.out.println("SubClass init!"); }}/** * do not actively use class fields to demonstrate */
public class NotInitialization {
    public static void main(String[] args){
        // Read a static field of the parent class, trigger the parent class initialization, then check whether the child class initialization is triggeredSystem.out.println(SubClass.value); }}Copy the code

Running results:

SuperClass init!
123
Copy the code

For static fields, only the class that directly defines the field is initialized. Therefore, referring to a static field defined in a parent class by its subclass only triggers initialization of the parent class, not the subclass. Whether or not subclass loading and validation is triggered is not specified in the virtual machine specification, depending on the implementation of the virtual machine.

Example 3 of passive quoting:

* Constants are stored in the constant pool of the calling class at compile time, and are not directly referenced to the class that defines them, so they do not trigger initialization of the class that defines them */
class ConstClass{
    static{
        System.out.println("ConstClass init!");
    }
    public static final String HELLOWORLD = "hello world";/ / final modification
}
/** * do not actively use class fields to demonstrate */
public class NotInitialization {
    public static void main(String[] args){ System.out.println(ConstClass.HELLOWORLD); }}Copy the code

Running results:

hello world

After the above code is run, there is no “ConstClass init! This is because the constant HELLOWORLD from ConstClass is referenced in Java source code, but it was optimized for constant propagation at compile time to store the value “Hello World” in the constant pool. References to NotInitialization are actually converted to references to the NotInitialization class’s own constant pool. That is, there is no reference point to a ConstClass in the NotInitialization Class file, and the two classes are no longer related to each other once they have been translated to Class.

Worth mentioning:

Like classes, interfaces also have an initialization process. You cannot use a “static{}” statement block in an interface, but the compiler still generates a “()” class constructor for the interface, which initializes member variables defined in the interface.

Interfaces really differ from classes in this way:

When a class is initialized, all of its parents are required to be initialized. However, when an interface is initialized, all of its parents are not required to be initialized. The parent interface is initialized only when it is actually used (for example, referencing constants defined in the interface).

3.3 Loading stage

  1. Get the binary byte stream that defines a class by its fully qualified name
  2. Convert the static storage structure represented by this byte stream to the runtime data structure of the method area.
  3. Generate an in-memory java.lang.Class object representing the Class as an access point to the Class’s various data information in the method area.

Add: How to load.class files:

  1. Load directly from the local system

  2. This parameter is obtained over the network. The typical scenario is a Web Applet

  3. Read from zip package, become the future jar, WAR format basis

  4. Runtime computational generation, most commonly used: dynamic proxy technology

  5. Generated by other files. Typical scenario: JSP applications extract. Class files from a proprietary database, which is rare

  6. Obtained from encrypted files, a typical protection against Class files being decompiled

3.4 Linking stage

3.4.1 Verify

To ensure that the byte stream of the Class file to be loaded meets the requirements of the CURRENT VM, ensuring that the loaded Class is correct and does not harm vm security.

It mainly includes four kinds of verification: file format verification, metadata verification, bytecode verification, symbol reference verification.

Tool for viewing bytecode files: Binary Viewer

Valid bytecode file: begins with the magic number CAFE BABE

If an invalid bytecode file is present, authentication will fail

At the same time, we can also install the IDEA plug-in Jclass to view our Class file, as follows

Remark:

Idea can also be installed plug-in, as follows

After the installation is complete, we compile a class file and click View to display our installed plug-in to view the bytecode methods

3.4.2 Prepare

Allocate memory for class variables [static variables] and set the default initial values of the class variables, that is, the integer type is zero and the reference type is null. The memory used by these variables will be allocated in the method area

public class HelloApp {
    private static int a = 1; // The value of a is 0 in the preparation phase and 1 in the next phase, at initialization
    public static void main(String[] args) { System.out.println(a); }}Copy the code

The above variable A is assigned an initial value in the preparation phase, but instead of 1, it is 0.

Static with final is not included here, because final is assigned at compile time and initialized explicitly during preparation. Class variables are allocated in the method area, while instance variables are allocated in the Java heap along with the object.

3.4.3 Resolve Resolve

Parsing is the process of converting symbolic references in a constant pool to direct references.

In fact, parsing operations are often performed with the JVM after initialization.

  • Symbolic References are a set of symbols that describe the target referenced. The literal form of symbolic references is clearly defined in the Class file format of the Java Virtual Machine Specification. A symbol can be any form of literal, as long as it is used unambiguously to the target.
  • A Direct Reference is a pointer to a target directly, a relative offset, or a handle to the target indirectly. If you have a direct reference. The referenced target must already exist in memory.

Parsing actions are for classes or interfaces, fields, class methods, interface methods, method types, and so on. CONSTANT Class Info, CONSTANT Fieldref Info, and CONSTANT Methodref Info in the CONSTANT pool in the Class file

3.5. Initialization phase

The class initialization stage is the last step in the class loading process. In the previous class loading process, except the user application can participate in the loading stage through the custom class loader, the rest of the actions are completely dominated and controlled by the virtual machine.

In the initialization phase, you actually start executing the Java program code (or bytecode) defined in the class.

The initialization phase is the execution of the class constructor method

(). That is, assignment to static variables in a class is done in code order according to the corresponding instructions in the constructor method

public class ClassInitTest {
    private static int num = 1;
    static {
        num = 2;
        number = 20;
        System.out.println(num);
        System.out.println(number); // Error, illegal forward reference. Because the number variable is declared later, it can't be called, it can be assigned
    }
    // This is ok, because the preparation phase assigns all class variables an initial value of 0, then 20 in order, and then overwrites here to 10
    private static int number = 10; 
    public static void main(String[] args) {
        System.out.println(ClassInitTest.num); / / 2
        System.out.println(ClassInitTest.number); / / 10}}Copy the code

2. This method does not need to be defined and is a combination of the statements in the static code block and the assignment actions that the Javac compiler automatically collects for all class variables in the class. That is, when we include static variables in our code, we have the

() method

The instructions in the constructor methods are executed in the order in which the statements appear in the source file.

4. < Clinit >() is different from the constructor of the class.

()

()

()

()

()

()





4. Unlike the class constructor (or instance constructor

() method), the

() method does not explicitly call the parent constructor, and the virtual machine ensures that the parent < Clinit >() method completes execution before the subclass’s < Clinit >() method executes.

So the first class of the

() method to be executed in the virtual machine must be java.lang.object. Since the

() method of the parent class executes first, it means that static blocks defined in the parent class take precedence over variable assignments in the child class. Look at the following code:

// About variable assignment when the parent class is involved
public class ClinitTest1 {
    static class Father {
        public static int A = 1;
        static {
            A = 2; }}static class Son extends Father {
        public static int b = A;
    }

    public static void main(String[] args) {
        System.out.println(Son.b);  / / 2}}Copy the code

The ClinitTest1 class is first loaded into memory, then the main method is called, and then the Son load initialization, which is its

() method, is performed. However, Son inherits Father, so the Father load initialization needs to be performed first. After the parent class is initialized, A is equal to 2, and I assign A to B and I assign 2. We decompile the load of Father, first we see that the original value of A is assigned to 1, then copied to 2, and finally returned

iconst_1
putstatic #2 <com/atguigu/java/chapter02/ClinitTest1$Father.A>
iconst_2
putstatic #2 <com/atguigu/java/chapter02/ClinitTest1$Father.A>
return
Copy the code

5. The virtual machine ensures that a class’s < Clinit >() methods are locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one thread will execute the class’s < Clinit >() methods, and all the other threads will block and wait. Until the active thread finishes executing the

()() method.

public class DeadThreadTest {
    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t thread T1 started");
            new DeadThread(); // The constructor creates the class object and executes the 
      
       () method
      
        }, "t1").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t thread T2 started");
            new DeadThread();
        }, "t2").start(); }}class DeadThread {
    static {
        If the if statement is not included, the compiler will say "Initializer does not complete normally" and refuse to compile
        if (true) { // When one thread is working, another thread cannot enter, which is equivalent to synchronizing the lock process
            System.out.println(Thread.currentThread().getName() + "\t initializes current class");
            while(true) {}}}}Copy the code

In the code above, the output is

Thread T1 starts thread T2 starts thread T2 initializes the current classCopy the code

From the printed results, you can see that once the class is initialized, only one initialization can be performed, that is, the

() method will be called once, so only one xx thread will initialize the current class, that is, one thread will be in an infinite loop to simulate a long operation, and another thread will be blocking and waiting. This is equivalent to the

() method of our class being locked synchronously in multiple threads.

The < Clinit >() method is not required for a class or interface, and the compiler may not generate the

() method for a class if there are no static blocks and no assignments to variables.

7, the interface cannot use static statement blocks, but there are still variable initialization assignment operations. But unlike classes, the

() method that executes the interface does not need to first execute the

() method of the parent interface. The parent interface is initialized only when a variable defined in the parent interface is used, and the implementation class of the interface does not execute the interface’s () method when initialized.

4. General introduction to class loaders

  1. The JVM supports two types of classloaders. Bootstrap ClassLoader and User-defined ClassLoader, that is, other class loaders.

  2. Conceptually, a custom class loader is a class of class loaders defined by developers in a program, but the Java Virtual Machine specification does not. Instead, we divide all classloaders derived from the abstract ClassLoader into custom classloaders (e.g., our extension ClassLoader, system ClassLoader (application ClassLoader)).

  3. No matter how classloaders are classified, the most common class loaders we have in Java programs are always just three, as follows:

5. Get a different class loader demo

public class ClassLoaderTest {
    public static void main(String[] args) {

        // Get the system class loader
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        // Get the extension class loader on top of it
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);

        // Try to get the boot class loader
        ClassLoader bootstrapClassLoader = extClassLoader.getParent()
        System.out.println(bootstrapClassLoader);//null
        
        // Get the loader for the custom class, which is the system class loader
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader); 

        // Get a String loader
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1); //null}}Copy the code

The results obtained

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null 
Copy the code

It can be seen from the results that the boot class loader cannot be obtained directly from the code, and the current loader used by the user-defined ClassLoaderTest class is the system class loader. The root loader is used to load the Java core libraries. The root loader is used to load Java core libraries. The root loader is used to load Java core libraries.

6. Start the ClassLoader (Bootstrap ClassLoader)

  • This class loading is implemented in C/C++ and is nested within the JVM.

  • It is used to load Java’s core libraries (

    /jre/lib/rt.jar, resources.jar, or the contents of sun.boot.class.path) to provide classes that the JVM itself needs

  • Does not inherit from ava.lang.ClassLoader. There is no parent loader.

  • The startup class loader cannot be referenced directly by a Java program

  • Loads extension classes and application classloaders and specifies them as parent classloaders.

  • For security reasons, Bootstrap only loads classes whose package names start with Java, Javax, and Sun. For example, String belongs to the java.lang.String package

7. Extension ClassLoader

  • Java language, implemented by sun.misc.Launcher$ExtClassLoader.

  • Derived from the ClassLoader class

  • The parent class loader is the initiator class loader

  • Load the class libraries from the directory specified by the java.ext.dirs system property, or from the JRE /lib/ext subdirectory (extension directory) of the JDK installation directory. If user-created JAR files are placed in this directory, classes in these JAR files are automatically loaded by the extension class loader.

8. Application class loader (System class loader, AppClassLoader)

  • Java language by sun. Misc. LaunchersAppClassLoader implementation

  • Derived from the ClassLoader class

  • The parent class loader is the extension class loader

  • It is responsible for loading the class libraries under the path specified by the environment variable classpath or the system property java.class.path

  • Class loading is the default class loader in a program, and it is generally used to load classes in Java applications

  • Through this. GetSystemclassLoader () method can get the class loader

9. View the directories that the startup class loader can load

Starting a class loader can only load classes in the Java /lib directory, as we’ve seen conceptually

package com.atguigu.java;
import java.net.URL;
import java.security.Provider;

/ * * *@author lemon
 * @create2021-12-13 13:31 * TO: A handful of green plums for wine */
public class ClassLoaderTest1 {
    public static void main(String[] args) {
        System.out.println("** Start class loader *****");
        // Get the API path that BootstrapClassLoader can load
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL url : urls) {
            System.out.println(url.toExternalForm());
        }

        // Select a class from the path above to see what its classloader is:
        // Return null, indicating root loader (boot class)
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader);//null}}Copy the code

Results: Yes

** Start the classloader ***** file:/D:/ Java /jdk18.. 0 _131/jre/lib/resources.jar
file:/D:/java/jdk18.. 0 _131/jre/lib/rt.jar
file:/D:/java/jdk18.. 0 _131/jre/lib/sunrsasign.jar
file:/D:/java/jdk18.. 0 _131/jre/lib/jsse.jar
file:/D:/java/jdk18.. 0 _131/jre/lib/jce.jar
file:/D:/java/jdk18.. 0 _131/jre/lib/charsets.jar
file:/D:/java/jdk18.. 0 _131/jre/lib/jfr.jar
file:/D:/java/jdk18.. 0 _131/jre/classes
null
Copy the code

10. User-defined class loaders

In everyday Java application development prior to JDK9, class loading was almost always performed by the above three types of loaders in conjunction with each other. If necessary, we could customize class loaders to customize the way classes were loaded.

Why custom class loaders?

  • Isolate and reload classes

  • Modify the way classes are loaded

  • Extended loading source for.class files

  • Preventing source code leakage

User-defined class loader implementation steps:

  • Developers can implement their own classloaders by inheriting the abstract java.lang. ClassLoader class to meet specific needs

  • Before JDK1.2, it was common to inherit the ClassLoader class and rewrite the 1oadClass () method to implement custom classloaders, but after JDK1.2 it is no longer recommended to override the 1oadClass () method. Instead, I suggest that you write your custom classloading logic in the findClass () method

  • When writing a custom class loader, if you don’t have too complicated requirements, you can inherit the URIClassLoader class directly. This way, you can avoid writing the findClass () method and how to get the bytecode stream yourself, making the custom class loader writing simpler.

11. About ClassLoader

ClassLoader class, which is an abstract class from which all subsequent classloaders inherit (excluding the launcher ClassLoader)

None of the following methods is abstract

Sun.misc.Launcher is a portal application for the Java virtual machine

How to obtain a ClassLoader

- Get the current ClassLoader: clazz.getClassLoader() - Get the current thread context ClassLoader: Thread.currentthread ().getContextClassLoader() - Get system ClassLoader: This getSystemClassLoader () - for the caller to this: DriverManager. GetCallerClassLoader ()Copy the code