JVM memory and garbage collection series: class loading subsystem

Class loading subsystem

Overview

image-20200705080719531

The complete picture is as follows

image-20200705080911284

If you want to write a Java virtual machine by yourself, what structure should you consider?

  • Class loader
  • Execution engine

The role of the class loader subsystem

The class loader subsystem is responsible for loading the Class file from the file system or the network, and the class file has a specific file identifier at the beginning of the file.

ClassLoader is only responsible for the loading of class files. As for whether it can run, it is determined by Execution Engine.

The loaded class information is stored in a memory space called the method area. In addition to class information, runtime constant pool information is also stored in the method area, and may also include string literals and numeric constants (this part of constant information is the memory mapping of the constant pool part in the Class file)

image-20200705081813409

  • The class file exists on the local hard disk, which can be understood as a template drawn by the designer on paper, and finally this template is loaded into the JVM when it is executed to instantiate n identical instances based on this file.
  • The class file is loaded into the JVM, which is called a DNA metadata template, and is placed in the method area.
  • The .class file->JVM-> eventually becomes a metadata template. This process requires a transportation tool (Class Loader) to play the role of a courier.

image-20200705081913538

Class loading process

For example, the following simple code

/**
 * 类加载子系统
 */
public class HelloLoader {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("我已经被加载啦");
    }
}

What is its loading process?

image-20200705082255746

The complete flow chart is shown below

image-20200705082601441

Loading phase

Obtain the binary byte stream that defines this class through the fully qualified name of a class

Convert the static storage structure represented by this byte stream into the runtime data structure of the method area

Generate a java.lang.Class object representing this class in memory as the access entry for various data of this class in the method area

How to load class files

  • Load directly from the local system
  • Obtained through the Internet, typical scenario: Web Applet
  • Read from the zip archive and become the basis of the jar and war formats in the future
  • Run-time calculation and generation, the most used is: dynamic proxy technology
  • Generated by other files, typical scenario: JSP application extracts .class files from a proprietary database, which is relatively rare
  • Obtained from encrypted files, typical protection measures against decompilation of Class files

Link phase

Verify

The purpose is to ensure that the information contained in the byte stream of the Class file meets the requirements of the current virtual machine, to ensure the correctness of the loaded class, and not to endanger the security of the virtual machine itself.

It mainly includes four verifications, file format verification, metadata verification, bytecode verification, and symbol reference verification.

Tool: Binary Viewer view

image-20200705084038680

If an illegal bytecode file appears, the verification will fail

At the same time, we can view our Class file by installing the IDEA plug-in

image-20200705090237078

After the installation is complete, after we have compiled a class file, click on view to display the plug-ins we installed to view the bytecode method.

image-20200705090328171

Prepare

Allocate memory for the class variable and set the default initial value of the class variable, that is, the zero value.

/**
 */
public class HelloApp {
    
    
    private static int a = 1;  // 准备阶段为0,在下个阶段,也就是初始化的时候才是1
    public static void main(String[] args) {
    
    
        System.out.println(a);
    }
}

The variable a above will be assigned an initial value in the preparation phase, but it is not 1, but 0.

The static modified with final is not included here, because final will be allocated during compilation and will be explicitly initialized in the preparation phase;

The instance variable will not be allocated and initialized here, the class variable will be allocated in the method area, and the instance variable will be allocated to the Java heap along with the object.

For example, the following code

Resolve

The process of converting symbol references in the constant pool into direct references.

In fact, the parsing operation is often accompanied by the JVM after the initialization is executed.

Symbol reference is a set of symbols to describe the referenced target. The literal form of symbol reference is clearly defined in the class file format of the "Java Virtual Machine Specification". A direct reference is a pointer that directly points to the target, a relative offset, or a handle that is indirectly located to the target.

The parsing action is mainly for classes or interfaces, fields, class methods, interface methods, method types, etc. Corresponding to CONSTANT Class info, CONSTANT Fieldref info, Constant Methodref info, etc. in the constant pool

Initialization phase

The initialization phase is the process of executing the class constructor method ().

This method does not need to be defined. The javac compiler automatically collects the assignment actions of all class variables in the class and merges the statements in the static code block.

  • In other words, when our code contains static variables, there will be a clinit method

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

() Different from the constructor of the class. (Association: The constructor is () from the perspective of the virtual machine). If the class has a parent class, the JVM will ensure that the () of the parent class has been executed before the () of the subclass is executed.

  • After any class is declared, a constructor is generated, and the default is an empty parameter constructor
public class ClassInitTest {
    
    
    private static int num = 1;
    static {
    
    
        num = 2;
        number = 20;
        System.out.println(num);
        System.out.println(number);  //报错,非法的前向引用
    }

    private static int number = 10;

    public static void main(String[] args) {
    
    
        System.out.println(ClassInitTest.num); // 2
        System.out.println(ClassInitTest.number); // 10
    }
}

About the variable assignment process 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);
    }
}

Our output result is 2, which means that when we first load ClinitTest1, we will find the main method and then perform Son's initialization, but Son inherits Father, so Father's initialization needs to be performed, and A is assigned to 2 at the same time. We get Father’s loading process through decompilation. First we see that the original value 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

The virtual machine must ensure that the () method of a class is synchronized and locked in multiple threads.


public class DeadThreadTest {
    
    
    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "\t 线程t1开始");
            new DeadThread();
        }, "t1").start();

        new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "\t 线程t2开始");
            new DeadThread();
        }, "t2").start();
    }
}
class DeadThread {
    
    
    static {
    
    
        if (true) {
    
    
            System.out.println(Thread.currentThread().getName() + "\t 初始化当前类");
            while(true) {
    
    

            }
        }
    }
}

The above code, the output result is

线程t1开始
线程t2开始
线程t2 初始化当前类

It can be seen from the above that after initialization, initialization can only be performed once, which is the process of synchronous locking

Classification of class loaders

The JVM supports two types of class loaders. They are Bootstrap ClassLoader and User-Defined ClassLoader.

Conceptually, a custom class loader generally refers to a class loader customized by the developer in the program, but the Java virtual machine specification does not define it as such. Instead, it loads all classes derived from the abstract class ClassLoader All are divided into custom class loaders.

No matter how the type of class loader is divided, there are always only three of our most common class loader in the program, as shown below:

image-20200705094149223

The four here are the containment relationship, not the upper and lower layers, nor the inheritance relationship of subsystems.

We pass a class and get its different loaders


public class ClassLoaderTest {
    
    
    public static void main(String[] args) {
    
    
        // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        // 获取其上层的:扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);

        // 试图获取 根加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);

        // 获取自定义加载器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);
        
        // 获取String类型的加载器
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);
    }
}

From the results obtained, it can be seen that the root loader cannot be obtained directly through the code, and the loader currently used by the user code is the system class loader. At the same time, we obtain the loader of the String type and find it is null, which means that the String type is loaded through the root loader, which means that Java's core class libraries are loaded using the root loader.

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

Loader that comes with the virtual machine

Start the class loader (bootstrap class loader, Bootstrap ClassLoader)

  • This class loading is implemented in C/C++ language and is nested inside the JVM.
  • It is used to load the core Java library (JAVAHOME/jre/1ib/rt.jar, resources.jar or sun.boot.class.path path), used to provide the classes needed by the JVM itself
  • It does not inherit from ava.lang.ClassLoader and has no parent loader.
  • Load extension classes and application class loaders, and specify them as their parent class loaders.
  • For security reasons, Bootstrap startup class loader only loads classes whose package names start with java, javax, sun, etc.

Extension ClassLoader

  • Written in Java language and implemented by sun.misc.Launcher$ExtClassLoader.
  • Derived from the ClassLoader class
  • The parent class loader is the startup class loader
  • Load the class library from the directory specified by the java.ext.dirs system property, or load the class library from the jre/1ib/ext subdirectory (extension directory) of the JDK installation directory. If the JAR created by the user is placed in this directory, it will also be automatically loaded by the extension class loader.

Application class loader (system class loader, AppClassLoader)

  • Written in javI language and implemented by sun.misc.LaunchersAppClassLoader
  • Derived from the ClassLoader class
  • The parent class loader is the extended class loader
  • It is responsible for loading the class library under the path specified by the environment variable classpath or the system property java.class.path
  • This class loading is the default class loader in the program. Generally speaking, Java application classes are loaded by it
  • The class loader can be obtained through the classLoader#getSystemclassLoader() method

User-defined class loader

In the daily application development of Java, the loading of classes is almost performed by the above three types of loaders. When necessary, we can also customize the class loaders to customize the way the classes are loaded.
Why do you want to customize the class loader?

  • Load class in isolation
  • Modify the way the class is loaded
  • Extension loading source
  • Prevent source code leakage

Implementation steps of user-defined class loader:

  • Developers can implement their own class loader by inheriting the abstract class ava.1ang.ClassLoader to meet some special needs
  • Before JDK1.2, when customizing the class loader, always inherit the ClassLoader class and rewrite the 1oadClass() method to achieve a custom class loading class, but after JDK1.2, it is no longer recommended for users to override 1oadclass() method, but it is recommended to write custom class loading logic in the findclass() method
  • When writing a custom class loader, if you do not have too complex requirements, you can directly inherit the URIClassLoader class, so that you can avoid writing your own findclass() method and the way to get the bytecode stream, so that you can make a custom class loader The writing is more concise.

View the directories that can be loaded by the root loader

We just learned through the concept that the root loader can only load classes in the java /lib directory. Let’s verify it with the following code


public class ClassLoaderTest1 {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("*********启动类加载器************");
        // 获取BootstrapClassLoader 能够加载的API的路径
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL url : urls) {
    
    
            System.out.println(url.toExternalForm());
        }

        // 从上面路径中,随意选择一个类,来看看他的类加载器是什么:得到的是null,说明是  根加载器
        ClassLoader classLoader = Provider.class.getClassLoader();
    }
}

The results obtained

*********启动类加载器************
file:/E:/Software/JDK1.8/Java/jre/lib/resources.jar
file:/E:/Software/JDK1.8/Java/jre/lib/rt.jar
file:/E:/Software/JDK1.8/Java/jre/lib/sunrsasign.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jsse.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jce.jar
file:/E:/Software/JDK1.8/Java/jre/lib/charsets.jar
file:/E:/Software/JDK1.8/Java/jre/lib/jfr.jar
file:/E:/Software/JDK1.8/Java/jre/classes
null

About ClassLoader

ClassLoader class, it is an abstract class, all subsequent class loaders inherit from ClassLoader (not including the startup class loader)

image-20200705103516138

sun.misc.Launcher It is the entry application of a java virtual machine

image-20200705103636003

Ways to obtain ClassLoader

  • Get the current ClassLoader: clazz.getClassLoader()
  • Get the ClassLoader of the current thread context: Thread.currentThread().getContextClassLoader()
  • Get the ClassLoader of the system: ClassLoader.getSystemClassLoader()
  • Get the caller's ClassLoader: DriverManager.getCallerClassLoader()

Parental delegation mechanism

The Java virtual machine uses an on-demand loading method for class files, which means that when the class needs to be used, its class file will be loaded into the memory to generate a class object. And when the class file of a certain class is loaded, the Java virtual machine adopts the parental delegation mode, that is, the request is handed over to the parent class for processing, which is a task delegation mode.

working principle

  • If a class loader receives a class loading request, it does not load it first, but delegates the request to the loader of the parent class to execute;
  • If the parent class loader still has its parent class loader, it will further delegate upwards, recursively, and the request will eventually reach the top-level startup class loader;
  • If the parent class loader can complete the class loading task, it returns successfully. If the parent class loader cannot complete the loading task, the child loader will try to load it by itself. This is the parent delegation mode.

image-20200705105151258

Examples of parent delegation mechanism

When we load jdbc.jar for database connection, first of all we need to know that jdbc.jar is implemented based on the SPI interface, so when loading, parent delegation will be carried out, and finally SPI will be loaded from the root loader The core class is then loaded with the SPI interface class, followed by reverse delegation, and the implementation class jdbc.jar is loaded through the thread context class loader.

image-20200705105810107

Sandbox security mechanism

Custom string class, but when loading the custom String class, it will be the first to use the boot class loader to load, and the boot class loader will first load the file that comes with jdk (java\lang in the rt.jar package) \String.class), the error message says that there is no main method because the string class in the rt.jar package is loaded. This can ensure the protection of the java core source code, which is the sandbox security mechanism.

Advantages of the parental delegation mechanism

Through the above example, we can know that the parent mechanism can

  • Avoid repeated loading of classes
  • Protect the security of the program and prevent the core API from being tampered with at will
    • Custom class: java.lang.String
    • Custom class: java.lang.ShkStart (error: prevent the creation of classes starting with java.lang)

other

How to judge whether two class objects are the same

There are two necessary conditions to indicate whether two class objects are the same class in the JVM:

  • The complete class name of the class must be the same, including the package name.
  • The ClassLoader (referring to the ClassLoader instance object) that loads this class must be the same.

In other words, in JvM, even if these two class objects (class objects) originate from the same Class file and are loaded by the same virtual machine, as long as the ClassLoader instance objects that load them are different, then the two class objects are different. equal.

The JVM must know whether a type is loaded by the boot loader or by the user class loader. If a type is loaded by the user class loader, the JVM will save a reference to this class loader in the method area as part of the type information. When resolving references from one type to another, the JVM needs to ensure that the class loaders of these two types are the same.

Active and passive use of classes

The use of classes in Java programs is divided into: Wang Dong use and passive use.
Active use is divided into seven situations:

  • Create an instance of the class
  • Access a static variable of a certain class or interface, or assign a value to the static variable
  • Call the static method of the class I
  • Reflection (for example: Class.forName("com.atguigu.Test"))
  • Initialize a subclass of a class
  • The class that is marked as the startup class when the Java virtual machine is started
  • Dynamic language support starting from JDK7:
  • The analysis result of java.lang.invoke.MethodHandle instance REF getStatic, REF putStatic, REF invokeStatic handle corresponding to the class is not initialized, then initialize

Except for the above seven cases, other methods of using Java classes are regarded as passive use of the class, and will not lead to the initialization of the class.

Guess you like

Origin blog.csdn.net/weixin_43314519/article/details/110410761