JVM1-Understand JVM: Official website to understand JVM, Java source file running process, what is a class loader, three ways of Java class loading mechanism, Tomcat's custom class loader

Learn about JVM through the official website

Official website jdk8 structure diagram

jdk8 documentation official website: https://docs.oracle.com/javase/8/docs/

insert image description here
insert image description here

What is JVM

JVM is a Java virtual machine, the full name is Java Virtual Machine . Is an abstract computing machine that has an instruction set and manipulates memory at runtime. The JVM can be imagined as a physical machine, and a physical machine must follow the Von Neumann computer model system .
Please add a picture description
The physical machine receives a combination of 0101, so the JVM also receives a combination of 0101, so the input device here is in the form of 0101. The bytecode file in the JVM is used as an input device, so it can be understood why the bytecode file is a binary file.

The Java virtual machine is ported to different platforms to provide hardware and operating system independence. The Java Platform Standard Edition provides two implementations of the Java Virtual Machine (VM):
①Java HotSpot client virtual machine
The client virtual machine is the implementation of the platform usually used for client applications.
Client VMs are optimized to reduce startup time and memory footprint.
It can be enabled by using the -client command line option when starting the application.

②Java HotSpot server virtual machine
The server virtual machine is an implementation designed to achieve the highest program execution speed, with a trade-off between startup time and memory.
It can be enabled by using the -server command line option when starting the application.

Some features of Java HotSpot technology are common to both VM implementations , as follows:

  • Adaptive Compiler - The application starts with a standard interpreter, but the code is then analyzed at runtime to detect performance bottlenecks or "hot spots". Java HotSpot VMs compile performance-critical parts of the code to improve performance while avoiding unnecessary compilation of rarely used code (most of the program). Java HotSpot VMs also use an adaptive compiler to dynamically determine how to optimize compiled code through techniques such as inlining. The runtime analysis performed by the compiler allows it to eliminate guesswork when determining which optimizations will yield the greatest performance benefit.
  • Fast Memory Allocation and Garbage Collection - Java HotSpot technology provides fast memory allocation for objects with a choice of fast, efficient, and advanced garbage collectors.
  • Thread Synchronization The Java programming language allows multiple concurrent paths of program execution (called "threads"). Java HotSpot technology provides a threading capability designed to be used in large shared-memory multiprocessor servers.

View the official website Java language and virtual machine specification

Official website Java language and virtual machine specification: https://docs.oracle.com/javase/specs/index.html

insert image description here
Find the Java version you need,
insert image description here
select the Java Virtual Machine Specification, and click HTML or PDF.

Java source file running process

Please add a picture description
Why does Java compile once and run everywhere?
The Java source file is compiled by Javac and called a .class file. After the bytecode file is compiled, it is handed over to the JVM to run the .class file. A process of running is obviously to hand over the bytecode file to the JVM for operation and circulation. .

Why hand over the bytecode file to the JVM to run?
Many different manufacturers of computers, different CPUs, including the evolution on the hardware, have evolved different operating systems, and the analysis of different operating systems may be different. In order to allow Java source files to run on different machines, so To shield different operating systems, you can do it by handing over the bytecode file to the JVM to run, which is why Java can compile once and run everywhere.

1. Java source files are compiled into bytecode files by Javac

How to manually compile a Java source file

Insert picture description here
Open a directory storing Java source files, enter the dos window, and execute the command

javac .\文件名

insert image description here
Test.class opens like this
insert image description here

How to read the compiled .class file on the official website

First, go to the official website Java language and virtual machine specification: https://docs.oracle.com/javase/specs/index.html
insert image description here

Find the Java version you need,
insert image description here
select the Java Virtual Machine Specification, and click HTML or PDF.
The fourth point is the class file format description: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
insert image description here

Official website class file structure format description

ClassFile {
    
    
    u4             magic;//字节码文件的规范,一般value是以0xCAFEBABE开头,俗称为魔术开头
    u2             minor_version;//最小版本号
    u2             major_version;//最大版本号
    u2             constant_pool_count;//常量池数量
    cp_info        constant_pool[constant_pool_count-1];//常量池,这里指的是静态常量池
    u2             access_flags;//访问比齐奥基
    u2             this_class;//当前类
    u2             super_class;//超类
    u2             interfaces_count;//接口数量
    u2             interfaces[interfaces_count];
    u2             fields_count;//字段数量
    field_info     fields[fields_count];
    u2             methods_count;//方法数量
    method_info    methods[methods_count];
    u2             attributes_count;//属性数量
    attribute_info attributes[attributes_count];
}

u2, u4, u8 are unsigned data types, which are 2 to the nth power.
Note: The constant pool in the class file structure refers to the static constant pool.
The constant pool is generally divided into three categories, static constant pool, runtime constant pool, and string constant pool. The constant pool mentioned here in the class file structure refers to the static constant pool.
Two things are stored in the static constant pool:
1. Literals: text; strings; final modified (not all final modified constants)
2. Symbolic references: some description information of classes, interfaces, fields, and methods

constant_pool is a table of structures representing various string constants, class and interface names, field names, and other constants referenced in structures and their substructures. The format of each entry is indicated by its first "flag" byte.

What is the content format in the bytecode file?

Use notepad to view the compiled .class file, you need to install the conversion plug-in

insert image description here

After installation, select the conversion

insert image description here

You can see that the bytecode file is formatted and displayed in hexadecimal. Is it essentially a hexadecimal file?
No, it is actually a binary file, which stores hexadecimal bytes, and its essence is a binary file.

javap decompiles the bytecode file and outputs it to the specified file

Execute the command in the current bytecode file directory (you can write the content casually, first grasp the output decompiled bytecode file, and the specific operation will be performed later), decompile the corresponding Test.class into assembly language, and output it as Test.txt file

 javap -p -v .\Test.class >Test.txt

Open the Test.txt file

insert image description here

2. Give the bytecode file compiled by Javac to the JVM to run

The process of handing over the .class file to the JVM run, that is, the class loading process (the life cycle of the class)

This involves the class loading mechanism that is often asked in interviews. As the name implies, it is to load the class into the JVM.
The description of this content in the official document is: loading, linking, initialization, the location is in Chapter 5, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
insert image description here

The class loading process (class life cycle) includes the following stages, loading, linking, initialization, use, and unloading.

Please add a picture description
Loading, verification, preparation, initialization, the order of occurrence of these four stages is fixed, but the parsing stage is not necessarily, because in some cases, the parsing operation can be performed after the initialization is completed, because there is a special scene in Java called Runtime binding, also known as late binding.

Loading, verification, preparation, initialization, these stages start in order, but do not necessarily end in order. Because these stages are usually cross-mixed, including validation.

In fact, in the middle of the entire class loading process, except for the loading phase, our users and applications can customize the class loader and we use the Java agent to enhance the bytecode, all other actions are dominated and controlled by the JVM, so when it comes to Initialization starts to execute the Java program code defined in the class, or bytecode, so the execution code is only the beginning here, limited to the class init method.

The process of class loading is mainly to load the bytecode file into the virtual machine.

1. Loading

The bytecode file needs to be read to the inside. The first thing to do is to find the bytecode file. If you want to find the bytecode file, the bytecode file must be a stream file, so the first step must be Yes: bytecode file ==> byte stream file .

The way to load the bytecode file:

  1. load from local system
  2. Download the .class file over the network. (package of applets)
  3. Load a .class file from an archive. (Archive files, including jar, war, and zip packages to extract, are all possible)
  4. Extracted from proprietary database, class files. (There will be such cases in jsp, but very few)
  5. Dynamically compile Java source files into .class files, that is, runtime calculation, that is, dynamic proxy
  6. Obtained from the encrypted file, that is, to prevent the .class file from being decompiled, so as to directly obtain the transfer information, so the file will be encrypted

Find the corresponding .class file according to the name. There may be duplicate names under different packages. Therefore, the binary byte stream of this class is obtained according to the fully qualified name of the Java source file. The prerequisite for obtaining the binary byte stream is, Binary files have been turned into binary byte streams.

So how to get the binary byte stream?
A byte stream search tool is needed. There is such a module in Java that can obtain an action such as a binary byte stream through the fully qualified name of the class, and this action is implemented outside the JVM. Why should it be placed in externally? Because the application has to decide how to obtain the required classes, it is not implemented inside the JVM. The code module that implements this action is called a class loader.

After obtaining the byte stream, it is necessary to convert the static storage structure represented by the byte stream into something that can be put into memory. How to put it?
What can be found must be a static storage structure. At this time, the first show that can be found must satisfy the static storage structure. At this time, the static storage structure represented by the byte stream will be converted into something put into the memory.
Method area: An area is divided in the memory, called the method area, which becomes the so-called runtime data structure in the method area, because it needs to be run at this time.
Heap: At the same time, at this time, a data entry needs to be generated. And this data access entry can also be generated in the memory at the same time. At this time, one will be generated in the middle of the heap, representing the access entry of these data that these data can be accessed. At this time, a data structure is used to store it, called the heap, that is Generate a class object as the data access entry representing this class, and the loading is completed at this time.

Summary: Loading does two things. After loading, there is a method area, which contains class information, static variables and constants. In the heap memory, only the class object representing the loaded corresponding class exists in the heap memory.

Loading is the most important part of the class loading process, because it is the stage that our Java programmers are most concerned about, and it is only at this stage that we can manipulate the class loader. Control what? At this stage, the class loader can be manipulated, such as custom class loader to load, or use Java Agent to complete the enhanced operation of bytecode (Java Agent is often used for architecture, writing open source components, etc., used in Bytecode enhancement during loading phase).

2. link

Linking can be subdivided into three phases, verification, preparation, and resolution.

verification phase

Verification is to ensure that the information contained in the byte stream information in the so-called bytecode file fully complies with the current JVM specification requirements, that is, ① it will verify that the bytecode file cannot make mistakes, and ② and the information cannot endanger the security of the JVM itself. is two actions.

File format verification
File format verification: It happens before entering the method area, and only after this stage of verification, the byte stream will enter the method area of ​​the memory for storage. The subsequent verification is not based on the storage in the method area, but based on the storage structure area in the method area.
for example:

  1. Whether to start with hexadecimal CAFEBABE
  2. Is the version number correct

Metadata verification
Metadata verification: perform semantic verification on the metadata information of the class. This metadata verification is the verified Java grammar. This verification can ensure that the metadata information that does not conform to the Java grammar specification cannot be entered. to the method area.
for example:

  1. Is there a parent class
  2. Whether to inherit the final class (final class cannot be inherited, there will be problems with inheritance)
  3. Whether a non-abstract class implements all abstract methods

Bytecode Verification
Bytecode Verification: Analyze data flow and control flow, mainly verify the harm of data to JVM, not necessarily grammatical errors

for example:

  1. run check
  2. Whether the stack data type matches the operation parameter of the opcode

Symbolic reference verification
Symbolic reference verification: This is the final stage of verification, which occurs when the symbolic reference is converted into a direct reference, that is, the resolution stage. Verify the information other than the class itself, such as the references of the constant pool. This is not class information, but the matching check after the various symbol references included is to ensure that the parsing action can be executed normally. Therefore, here verification performed.

for example:

  1. Whether the description class exists in the constant pool
  2. Whether the accessed method or field exists and has sufficient permissions
Preparation Phase

Allocate memory for class variables (class variables are static variables, variables modified by static), and set the default initial value of Java for class variables, that is, assign a zero value.
At this time, under normal circumstances, the default initial value of the class variable, that is to say, the zero value of the current type will be different, as follows:

type of data zero value
int 0
long 0L
short (short)0
char ‘\u0000’
byte (byte)0
boolean false
float 0.0f
double 0.0d
reference (reference type) null

It will not allocate space for so-called instance variables (class variables are modified with static, which means that they need to be initialized, and instance variables refer to variables without static modification). Class variables will directly allocate space in the method area, while instance variables will be allocated with The so-called objects are allocated together in the Java heap.

private static int a = 1;

In the above code, in the actual class loading stage, we need to really open up a space for it in memory. In the preparation stage, a = 0. Here, the default value 1 defined by our code will not be assigned to the instance variable, but the value assigned The default value of Java's int type is 0.

private final static int a = 1;

Add final modification. In fact, there will be a special attribute called ConstantValue in the field table attribute of the class. In the preparation stage, this variable will be assigned and initialized to the value pointed to by the ConstantValue attribute. The function of the ConstantValue attribute is Notify the JVM to automatically assign values ​​​​to static variables. Only those modified by static can use this attribute, and the assignment of variables of non-static type is performed in the constructor.

There are two types of static modification, one is to add final modification to mark the variable as a constant, and the other is not to add final, but to assign a value in the class constructor, or the class init method, or the ConstantValue attribute is directly assigned in the preparation phase.

So in the program, when will ConstantValue be used? Only constants modified by static and final at the same time will have this attribute, and it is only based on basic types and String. When compiling, Javac will automatically generate this constant for this constant. An attribute such as ConstantValue, so that we can set the value of this ConstantValue constant in the preparation stage, and perform an assignment operation, that is to say, the static final modification will be directly assigned in this stage of preparation, and does not need To open up memory, if it is not a basic data type or a string, then we will choose to initialize it in the class constructor, that is, class init to do it.

So why limit to basic data types, and String?
Because of the constant pool, the constant pool can only refer to basic data types and String, so this is why ConstantValue can only refer to basic data types and String.

During the prepare phase, the JVM will provide

public class TestJvm {
    
    

    private static final int A=1;// 编译时Javac实际上就会为A生成ConstantValue属性,在准备阶段,JVM就会根据ConstantValue它的值,将A赋值为1,可以理解为在编译期就已经将结果放入了调用它的常量池当中

    public static void main(String[] args) {
    
    
        System.out.println(A);
    }
}

insert image description here

The evening content after a complete decompilation is as follows:

Classfile /D:/HAOKAI/haokai-framework/haokai-common/src/main/java/com/haokai/common/test/TestJvm.class
  Last modified 2023-5-29; size 452 bytes
  MD5 checksum 9ebfc3d20bdc8b726ca5249d1bad64fe
  Compiled from "TestJvm.java"
public class com.haokai.common.test.TestJvm
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #19.#20        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Class              #21            // com/haokai/common/test/TestJvm
   #4 = Methodref          #22.#23        // java/io/PrintStream.println:(I)V
   #5 = Class              #24            // java/lang/Object
   #6 = Utf8               A
   #7 = Utf8               I
   #8 = Utf8               ConstantValue
   #9 = Integer            1
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               SourceFile
  #17 = Utf8               TestJvm.java
  #18 = NameAndType        #10:#11        // "<init>":()V
  #19 = Class              #25            // java/lang/System
  #20 = NameAndType        #26:#27        // out:Ljava/io/PrintStream;
  #21 = Utf8               com/haokai/common/test/TestJvm
  #22 = Class              #28            // java/io/PrintStream
  #23 = NameAndType        #29:#30        // println:(I)V
  #24 = Utf8               java/lang/Object
  #25 = Utf8               java/lang/System
  #26 = Utf8               out
  #27 = Utf8               Ljava/io/PrintStream;
  #28 = Utf8               java/io/PrintStream
  #29 = Utf8               println
  #30 = Utf8               (I)V
{
    
    
  private static final int A;
    descriptor: I
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1

  public com.haokai.common.test.TestJvm();
    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

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: iconst_1
         4: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
         7: return
      LineNumberTable:
        line 8: 0
        line 9: 7
}
SourceFile: "TestJvm.java"

parsing stage

insert image description here

The passage introduced on the official website of the analysis stage actually has one meaning, symbolic references are transformed into direct references.
One thing is done in the parsing phase, which is to convert the symbolic references in the class into direct references. The symbol reference in the decompiled bytecode file is used to describe the target of the reference, which can be any literal value. As shown below:

insert image description here

Symbolic references: are in the file and have not been loaded into memory.
Direct reference: These symbols refer to it to become a so-called runtime data structure, which actually points to the address of the target, and it stores a pointer. In other words, a direct reference is a pointer directly to the target. For the content of the symbolic reference on the file, memory is actually allocated for it and pointed to it. That is to put the things on the file into the memory.

So one thing to do in the parsing stage is to replace the symbolic references in the constant pool (Constant pool) with direct references, and direct references are related to the JVM memory layout. The same symbol reference is on different JVMs. Direct quotes translated from above are generally not the same.

If there is a direct reference, the target of the direct reference must be in memory, but the symbolic reference is not necessarily. Because a symbol reference refers to those identifiers on the file, it is called a symbol.

Then the same symbol may be parsed multiple times. Except for the dynamic invokedynamic instruction (lambda expression), the JVM can cache the first parsing result. That is to say, except for the invokedynamic instruction, as long as the symbol reference is parsed If it becomes a direct reference, it will be cached to avoid repeated parsing actions. As for whether to execute multiple parsing operations, at this time, in fact, the JVM guarantees that if a symbol reference has been parsed, then the subsequent parsing will return directly. As a result of its success, if the first parsing fails, then subsequent parsing will also fail.

Dynamic invokedynamic instruction (lambda expression)

The dynamic invokedynamic instruction is actually a new JVM instruction introduced in the middle of Java7. It is also the first time to add a new instruction after 1.0. Although it was introduced in Java7, it was not used until Java8.

There is a feature in Java8, lambda expressions. It differs from other invoke execution methods in that it allows methods at the application level to determine method resolution, which means dynamic resolution is required, so the invokedynamic instruction is added.

You only need to know this content, and the content of symbolic references will be cached, except for invokedynamic instructions, that is to say, except for lambda expressions, you only need to know this.

3. Initialization

The simple point of the initialization phase is the process of executing the class init method.
Initialization can be divided into three cases: class variable initialization, class initialization, and class initialization with a direct parent class.

Class variable initialization: In the preparation stage, the class variable has been assigned the default value required by the system once, but this is the default value assigned by the JVM, and in the initialization stage, it needs to be assigned the value that the programmer really wants to give it . In the middle of Java, there are only two ways to set the initial value of the class variable:

  1. Instruction class variable value: directly specify the class variable value
  2. static block

According to the programmer's logic, static variables must be defined before static code blocks. Because the execution of these two is based on the order in which the code is written, that is to say, static variables need to be written before the static code block, and the wrong order may affect the business code.

Class initialization: When initializing a class, the JVM is loaded on demand, and will only be loaded when it is used. If the class needs to be used, the two steps of loading and linking have not been performed, and these two steps need to be performed first. Two steps to continue initialization.

Class initialization with a direct parent class: If this class has a direct parent class, then at this time you need to initialize the parent class before initializing the subclass. If there are initialization statements in the class, these initialization statements will also be executed once.

When will initialization be triggered?

When will initialization be triggered? In other words, when is the class initialized?
In fact, only when this class is used will it go back to initialize, because it comes on demand, that is, when it is actively used, it will cause the class to be initialized, which is called active reference of the class .

Active citations are divided into the following 6 situations:

  1. Create an instance of a class, which is the new way
  2. Access a static variable of a class or interface, or assign a value to the static variable
  3. Calling a static method of a class
  4. Reflection (e.g. Class.forName("com.aaa.Test"))
  5. Initialize a subclass of a class, its parent class will also be initialized
  6. When the Java virtual machine is started, it is marked as the startup class (JvmCaseApplication, which is the class with the main method), directly use the java.exe command to run a main class,

Passive reference: There is another situation where other classes may be used inadvertently, but the class is not initialized. This situation is called passive reference.
There are three types of passive references:

  1. Subclasses refer to the static fields of the parent class, which will only cause the initialization of the parent class, but not the initialization of the subclass
  2. Using the final constant of the class will not cause the initialization of the class, because the constant assignment is ConstantValue
  3. Defining a class group does not cause initialization of the class

Active citations and passive citations will be asked in the interview, and there will also be written test questions

uninstall

There are very, very few cases where a class is uninstalled, and the following three conditions must be met at the same time before it will be uninstalled:

  1. All instances of this class have been recycled, that is, there are no instances of this class in the Java heap
  2. The ClassLoader that loaded this class (ClassLoader is the class loader that loaded this class) has been recycled
  3. The java.lang.Class object corresponding to this class is not referenced anywhere, and the method of this class cannot be accessed through reflection anywhere

The JVM itself will refer to these ClassLoaders, and these ClassLoaders themselves will always refer to the Class object that loads it. Therefore, in general, the requirements of 2 and 3 are difficult to achieve.

But there is a situation that will be reached, such as a custom class loader, which needs to be unloaded, and its System.gc() will be called under normal circumstances. System.gc() just notifies the JVM that it wants to do a GC operation. The GC will be notified to recycle, not immediately after calling, so generally after System.gc(), it will sleep for 500ms~1000ms.

Therefore, the unloading time is uncertain. Generally, if the class loader needs to be customized, there is generally no need to develop the unloading part. From the perspective of normal business code, 2 and 3 are not accessible, because they cannot be uninstalled. In normal code, the recycling mechanism will not be written to the general page.

Diagram of what the class loading mechanism should do

Please add a picture description

What is a class loader?

A class loader is a code module responsible for reading Java bytecode code and converting it into an instance of java.lang.Class.

Another function is to determine the uniqueness of the class in the virtual machine?
What does it mean? A class is unique when it is loaded into the same class loader. Different class loaders allow classes with the same name to exist, but the same class loader does not allow classes with the same name to exist.

For example, if you build a class like java.lang.String yourself, it can also be loaded normally. This is because class loaders are hierarchical, and class loaders are hierarchical.

Please add a picture description

code:

public class Demo {
    
    

    public static void main(String[] args) {
    
    
        // AppClassLoader
        System.out.println(new Demo().getClass().getClassLoader());
        // ExtClassLoader
        System.out.println(new Demo().getClass().getClassLoader().getParent());
        // Bootstrap ClassLoader
        System.out.println(new Demo().getClass().getClassLoader().getParent().getParent());
        System.out.println(new String().getClass().getClassLoader());

        /**
         * 输出结果如下:之所以还有null,是因为Bootstrap ClassLoader的本质是C层面的东西,在Java层面看不到
         * sun.misc.Launcher$AppClassLoader@18b4aac2
         * sun.misc.Launcher$ExtClassLoader@1e80bfe8
         * null
         * null
         */
    }
}

Three ways of Java's class loading mechanism

1. Take full responsibility

The overall responsibility mechanism is also called the current class loading mechanism. When a class loader is responsible for loading a class, other classes it depends on and references should be loaded by the current class loader, unless you explicitly request to use another loader, otherwise it is the current class loader loader together.

2. Parent class delegation

This is the so-called parental delegation mechanism (the reason why it is called parental delegation is a translation problem). For example, if you customize a String class and the class loader responsible for loading is AppClassLoader, then it will still go up to find a class loader with a higher trust level, ExtClassLoader, Bootstrap ClassLoader, if Bootstrap ClassLoader does not have it, it will to load the lower level. Only when the loader of the parent class cannot find the bytecode file, it will search and load the target class from its own class path. The specific process of entrusted loading by the parent class: First, it must be judged whether the top-level class is loaded. If the top-level class is loaded, the rest are ignored. Only one class is always loaded, and only the higher-level class is always loaded. Parent class delegation is just a mechanism recommended by Java, and you can implement your own class loader by inheriting its ClassLoader.

3. Caching mechanism

It will ensure that all loaded classes are cached in memory. When the program needs to use a certain class. The class loader must first search for the class in the area of ​​​​the memory first, and only when the cache area does not exist, it will read the binary data corresponding to the class, and then convert it into the corresponding class object. If there is, don’t read it, which is why when the class is modified, the JVM must be restarted , and the restart will take effect.

For class loaders, the same fully qualified name is only loaded once.

And jdk8 uses direct memory, that is, meta space, so we will use direct memory for caching, which is why the class variable command is initialized once. This can be found in the source code of ClassLoader:
loadClass() method

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    
    
        synchronized (getClassLoadingLock(name)) {
    
    
            // First, check if the class has already been loaded
            // 第一步再虚拟机的内存当中检查你需要选择的类是否已经加载完成
            Class<?> c = findLoadedClass(name);// 这个方法就是类缓问题存在的主要方法
            if (c == null) {
    
    
                long t0 = System.nanoTime();
                try {
    
    
                	// 这段代码就是父类委派机制的代码
                    if (parent != null) {
    
    // 之前有说过,有父类即代表parent不为空,因此会一直找上层
                        c = parent.loadClass(name, false);
                    } else {
    
    
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
    
    
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
    
    
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);// 如果都没有,会调用findClass(name)这个方法,所以大部分重写类加载器只要重写这个方法即可,并不需要整体重写
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
    
    
                resolveClass(c);
                /**
                * 该方法是字节码加载到内存中进行到链接操作,也就是说在这个方法中,
                * 会对文件格式以及字节码进行一个验证,并且在这里会为static修饰的开辟初始空间,
                * 即准备阶段也是在这里完成的,包括符号引用转换为直接引用,访问控制以及方法覆盖都是在这个方法里的
                */
            }
            return c;
        }
    }

Parent delegation (parent delegation)

The parent class delegation model is actually not a mandatory model, it will bring some problems, for example, there is a class in Java called java. , and it is our database manufacturer who provides the implementation, and the provider's dependent library cannot be placed under Bootstrap ClassLoader. It should belong to the extended class, that is, ExtClassLoader. If this mechanism is entrusted according to the parent class, then in Bootstrap Loading in the ClassLoader area can only be loaded to the interface of java.sql.Driver, but not to its implementation, so it is necessary to break the parent class delegation mechanism.

There is a special way to break the parent class delegation mechanism. For example, in Java1.6, there is a way called SPI (Service Provide interface, service provider interface), that is, as long as jdk provides an interface similar to java.sql.Driver, the supplier To provide services, when programmers code, they program directly to the interface and directly implement it, and then jdk can automatically find this implementation, including Java defining many interfaces in the core class library, and we will do call logic for these interfaces.

Java is a static language. If the language cannot be dynamic, how can the program be made dynamic?
Code hot deployment, code hot replacement, that is, the machine can be used for deployment without restarting, but there will be problems in this way. It is the so-called OSGI, which can realize modular hot deployment. Does it do this? It customizes a class loader, and then each program module will have its own class loader. When a program module needs to be replaced, it will kill the program module together with the class loader to achieve code hot replacement. , it works, it works, but it's so disgusting that basically no one uses it. At this time, there is another solution, that is, we customize the class loader.

custom class loader

Reasonable use of custom class loaders, it is best not to rewrite the loadClass() method, and it is best not to rewrite the findClass() method, because it will destroy the parent delegation mechanism.

package com.haokai.common.test;

import java.io.*;

public class MyClassLoader extends ClassLoader {
    
    
    private String root;

    /**
     * 核心在于字节吗文件的获取,这里如果字节码有加密。需要在这里进行相应的解密操作
     *
     * @return
     * @throws ClassNotFoundException
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
        byte[] classData = loadClassData(name);
        if (classData == null) {
    
    
            throw new ClassNotFoundException();
        } else {
    
    
            // 此方法负责将二进制的字节码转换为class对象
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
    
    
        String fileName = root +
                File.separatorChar + className.replace('.', File.separatorChar)
                + ".class";
        try {
    
    
            InputStream ins = new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length = 0;
            while ((length = ins.read(buffer)) != -1) {
    
    
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

    public String getRoot() {
    
    
        return root;
    }

    public void setRoot(String root) {
    
    
        this.root = root;
    }

    public static void main(String[] args) {
    
    
        MyClassLoader loaderDemo1 = new MyClassLoader();
        MyClassLoader loaderDemo2 = new MyClassLoader();
        // 这里传入的一定是类的全限定名,也就是你存放该class文件的路径
        loaderDemo1.setRoot("D:\\HAOKAI\\haokai-framework\\haokai-common\\src\\main\\java");
        loaderDemo2.setRoot("D:\\classPath");
        Class<?> demo1Class = null;
        Class<?> demo2Class = null;
        try {
    
    
            // 这里传入的一定是类的全限定名,包括文件需要给到相应的权限
            demo1Class = loaderDemo1.loadClass("com.haokai.common.test.TestDemo");
            System.out.println(demo1Class);
            Object demo1 = demo1Class.newInstance();
            System.out.println(demo1.getClass().getClassLoader());

            // Demo2的java文件和class文件不放在放在类路径下
            demo2Class = loaderDemo2.loadClass("TestDemo");
            System.out.println(demo2Class);
            Object demo2 = demo2Class.newInstance();
            System.out.println(demo2.getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (InstantiationException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        }
    }
}

The core of the custom class loader is to obtain the bytecode file. If the bytecode is encrypted, the file needs to be decrypted in this class
. The custom class loader is often used for encryption. Since this is just a demonstration, I have not encrypted the class file, so there is no decryption process. Here are a few points to note:

  1. The filename passed here needs to be the fully qualified name of the class, as the defineClass method handles this format.
    If there is no fully qualified name, then what we need to do is to load the full path of the class, and our setRoot is the prefix address. The path of setRoot + loadClass is the absolute path of the file
  2. It is best not to override the loadClass method, because it is easy to break the parental delegation model.
  3. The com.haokai.common.test.TestDemo class in the class path can be loaded by the AppClassLoader class, so if we want to use a custom class loader for loading, we cannot put TestDemo.class in the class path. Otherwise, due to the existence of the parental delegation mechanism, the parent class loader will be used for loading in the current class path, which will directly cause the class to be loaded by AppClassLoader instead of our custom class loader.

Note when running, you need to manually compile the Java file into a class file first, the following are the codes of the two TestDemo

insert image description here
Under the current class path, the parent class loader will be used first for loading, which will directly cause the class to be loaded by AppClassLoader, com.haokai.common.test.TestDemo, note that there is a package name here

package com.haokai.common.test;

public class Demo2 {
    
    
    public static void main(String[] args) {
    
    
        System.out.println();
    }
}

Use a custom class loader to load, do not put it in the class path, define a path yourself, TestDemo, note that there is no package name
insert image description here
here

public class TestDemo {
    
    
    public static void main(String[] args) {
    
    
        System.out.println();
    }
}

The output is as follows: You can see that the second one uses MyClassLoader, the loader we defined ourselves

class com.haokai.common.test.TestDemo
sun.misc.Launcher$AppClassLoader@18b4aac2
class TestDemo
com.haokai.common.test.MyClassLoader@1e80bfe8

Tomcat's custom classloader

Tomcat also rewrites the class loader. The directory where Tomcat's custom class loader is located is version 8.0 and versions after 8.5 are not in the same directory, and version 8.0 is outdated.

In versions after tomcat8.5, put it in src\java\org\apache\catalina\loader.
Download the source code, I use version 8.5.89 here, https://tomcat.apache.org/download-80.cgi

insert image description here

Official website link, click to copy the following address to trigger the download: https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.89/src/apache-tomcat-8.5.89-src.zip
cloud disk link: Source code download-apache-tomcat-8.5.89-src

After decompression, enter the apache-tomcat-8.5.89-src\java\org\apache\catalina\loader directory, find ClassLoader related classes,

insert image description here

Open to find WebappClassLoader extends WebappClassLoaderBase, find the findClass() method of WebappClassLoaderBase:

@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
    
    

    if (log.isDebugEnabled()) {
    
    
        log.debug("    findClass(" + name + ")");
    }

    checkStateForClassLoading(name);

    // (1) Permission to define this class when using a SecurityManager
    if (securityManager != null) {
    
    
        int i = name.lastIndexOf('.');
        if (i >= 0) {
    
    
            try {
    
    
                if (log.isTraceEnabled()) {
    
    
                    log.trace("      securityManager.checkPackageDefinition");
                }
                securityManager.checkPackageDefinition(name.substring(0,i));
            } catch (Exception se) {
    
    
                if (log.isTraceEnabled()) {
    
    
                    log.trace("      -->Exception-->ClassNotFoundException", se);
                }
                throw new ClassNotFoundException(name, se);
            }
        }
    }

    // Ask our superclass to locate this class, if possible
    // (throws ClassNotFoundException if it is not found)
    Class<?> clazz = null;
    try {
    
    
        if (log.isTraceEnabled()) {
    
    
            log.trace("      findClassInternal(" + name + ")");
        }
        try {
    
    
            if (securityManager != null) {
    
    
                PrivilegedAction<Class<?>> dp =
                    new PrivilegedFindClassByName(name);
                clazz = AccessController.doPrivileged(dp);
            } else {
    
    
                clazz = findClassInternal(name);
            }
        } catch(AccessControlException ace) {
    
    
            log.warn(sm.getString("webappClassLoader.securityException", name,
                    ace.getMessage()), ace);
            throw new ClassNotFoundException(name, ace);
        } catch (RuntimeException e) {
    
    
            if (log.isTraceEnabled()) {
    
    
                log.trace("      -->RuntimeException Rethrown", e);
            }
            throw e;
        }
        if ((clazz == null) && hasExternalRepositories) {
    
    
            try {
    
    
                clazz = super.findClass(name);
            } catch(AccessControlException ace) {
    
    
                log.warn(sm.getString("webappClassLoader.securityException", name,
                        ace.getMessage()), ace);
                throw new ClassNotFoundException(name, ace);
            } catch (RuntimeException e) {
    
    
                if (log.isTraceEnabled()) {
    
    
                    log.trace("      -->RuntimeException Rethrown", e);
                }
                throw e;
            }
        }
        if (clazz == null) {
    
    
            if (log.isDebugEnabled()) {
    
    
                log.debug("    --> Returning ClassNotFoundException");
            }
            throw new ClassNotFoundException(name);
        }
    } catch (ClassNotFoundException e) {
    
    
        if (log.isTraceEnabled()) {
    
    
            log.trace("    --> Passing on ClassNotFoundException");
        }
        throw e;
    }

    // Return the class we have located
    if (log.isTraceEnabled()) {
    
    
        log.debug("      Returning class " + clazz);
    }

    if (log.isTraceEnabled()) {
    
    
        ClassLoader cl;
        if (Globals.IS_SECURITY_ENABLED){
    
    
            cl = AccessController.doPrivileged(
                new PrivilegedGetClassLoader(clazz));
        } else {
    
    
            cl = clazz.getClassLoader();
        }
        log.debug("      Loaded by " + cl.toString());
    }
    return clazz;

}

Why rewrite? Sometimes we need to use some extension classes, Java will provide some interface specifications of the extension classes by default, but if we want to operate, we need to implement its class loader, because we don’t want it to be loaded into its parent class go up.

Guess you like

Origin blog.csdn.net/qq_41929714/article/details/130702011