This article is still based on the JDK8 version, with some changes from the JDK9 modularizer to the class loader.
0 javac compilation
java code
public class Math { public static final int initData = 666; public static User user = new User(); public int compute() { int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math = new Math(); math.compute(); System.out.println("end"); } }
javac compilation, javap -v -p view class file
Classfile /F:/workspace/advanced-java/target/classes/com/lzp/java/jvm/classloader/Math.class // Part 1, description information: size, modification time, md5 value, etc Last modified 2022 January 8; size 1006 bytes MD5 checksum 4cece4543963b23a98cd219a59c1887c Compiled from "Math.java" // Part 2, Description: compiled version public class com.lzp.java.jvm.classloader.Math minor version: 0 major version: 52 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #2 // com/lzp/java/jvm/classloader/Math super_class: #11 // java/lang/Object interfaces: 0, fields: 2, methods: 4, attributes: 1 // Part 3, constant pool information Constant pool: #1 = Methodref #11.#39 // java/lang/Object."<init>":()V #2 = Class #40 // com/lzp/java/jvm/classloader/Math #3 = Methodref #2.#39 // com/lzp/java/jvm/classloader/Math."<init>":()V #4 = Methodref #2.#41 // com/lzp/java/jvm/classloader/Math.compute:()I #5 = Fieldref #42.#43 // java/lang/System.out:Ljava/io/PrintStream; #6 = String #44 // end #7 = Methodref #45.#46 // java/io/PrintStream.println:(Ljava/lang/String;)V #8 = Class #47 // com/lzp/java/jvm/classloader/User #9 = Methodref #8.#39 // com/lzp/java/jvm/classloader/User."<init>":()V #10 = Fieldref #2.#48 // com/lzp/java/jvm/classloader/Math.user:Lcom/lzp/java/jvm/classloader/User; #11 = Class #49 // java/lang/Object #12 = Utf8 initData #13 = Utf8 I #14 = Utf8 ConstantValue #15 = Integer 666 #16 = Utf8 user #17 = Utf8 Lcom/lzp/java/jvm/classloader/User; #18 = Utf8 <init> #19 = Utf8 ()V #20 = Utf8 Code #21 = Utf8 LineNumberTable #22 = Utf8 LocalVariableTable #23 = Utf8 this #24 = Utf8 Lcom/lzp/java/jvm/classloader/Math; #25 = Utf8 compute #26 = Utf8 ()I #27 = Utf8 a #28 = Utf8 b #29 = Utf8 c #30 = Utf8 main #31 = Utf8 ([Ljava/lang/String;)V #32 = Utf8 args #33 = Utf8 [Ljava/lang/String; #34 = Utf8 math #35 = Utf8 MethodParameters #36 = Utf8 <clinit> #37 = Utf8 SourceFile #38 = Utf8 Math.java #39 = NameAndType #18:#19 // "<init>":()V #40 = Utf8 com/lzp/java/jvm/classloader/Math #41 = NameAndType #25:#26 // compute:()I #42 = Class #50 // java/lang/System #43 = NameAndType #51:#52 // out:Ljava/io/PrintStream; #44 = Utf8 end #45 = Class #53 // java/io/PrintStream #46 = NameAndType #54:#55 // println:(Ljava/lang/String;)V #47 = Utf8 com/lzp/java/jvm/classloader/User #48 = NameAndType #16:#17 // user:Lcom/lzp/java/jvm/classloader/User; #49 = Utf8 java/lang/Object #50 = Utf8 java/lang/System #51 = Utf8 out #52 = Utf8 Ljava/io/PrintStream; #53 = Utf8 java/io/PrintStream #54 = Utf8 println #55 = Utf8 (Ljava/lang/String;)V { // The fourth part is variable information public static final int initData; descriptor: I flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 666 public static com.lzp.java.jvm.classloader.User user; descriptor: Lcom/lzp/java/jvm/classloader/User; flags: (0x0009) ACC_PUBLIC, ACC_STATIC public com.lzp.java.jvm.classloader.Math(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/lzp/java/jvm/classloader/Math; // Part V, method information public int compute(); descriptor: ()I flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=4, args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: bipush 10 9: imul 10: istore_3 11: iload_3 12: ireturn LineNumberTable: line 9: 0 line 10: 2 line 11: 4 line 12: 11 LocalVariableTable: Start Length Slot Name Signature 0 13 0 this Lcom/lzp/java/jvm/classloader/Math; 2 11 1 a I 4 9 2 b I 11 2 3 c I public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class com/lzp/java/jvm/classloader/Math 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: aload_1 9: invokevirtual #4 // Method compute:()I 12: pop 13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 16: ldc #6 // String end 18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 21: return LineNumberTable: line 16: 0 line 17: 8 line 18: 13 line 19: 21 LocalVariableTable: Start Length Slot Name Signature 0 22 0 args [Ljava/lang/String; 8 14 1 math Lcom/lzp/java/jvm/classloader/Math; MethodParameters: Name Flags args static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=2, locals=0, args_size=0 0: new #8 // class com/lzp/java/jvm/classloader/User 3: dup 4: invokespecial #9 // Method com/lzp/java/jvm/classloader/User."<init>":()V 7: putstatic #10 // Field user:Lcom/lzp/java/jvm/classloader/User; 10: return LineNumberTable: line 6: 0 }
Method, you can find the corresponding symbol in ConstantPool.
Refer to bytecode instruction table: https://docs.oracle.com/javase/specs/jvms/se8/html/index.html.
Class 1 loading process
j the classic class loading process is shown in the following figure, including loading, linking and initialization.
1.1 loading class files
The bytecode file is located on the disk. When a Class is used (for example, calling the main() method and new object), find and read the binary stream of the file in the disk through IO, convert it into the data structure of the method area, store it in the method area, and generate Java in the Java heap Lang. Class object. The Class object is an access portal to the method area. It is used for java reflection mechanism to obtain various information of the Class.
1.2 linking process
Verify: verify whether the class file conforms to the specification
-
Verification of file format. Verify whether it starts with 0XCAFEBABE and whether the version number is reasonable
-
Metadata validation. Whether there is a parent class, whether it inherits the final class (the final class cannot be inherited), and whether the non abstract class implements all abstract methods.
-
Bytecode verification. (omitted)
-
Symbol reference validation. The constant pool describes whether the class exists and whether the accessed methods or fields exist and have sufficient permissions.
-Xverify:none // Cancel validation
Preparation: allocate memory for static variables of the class and initialize to the initial value of the system
Variables modified by final static: directly assign values to user-defined values, such as private final static int value=123 and directly assign 123.
private static int value=123, the value of this stage is still 0.
Parsing: converting symbolic references to direct references (static links)
Each method and method parameter in Java code are symbols, and the class is loaded into the Constant pool in the method area.
Symbolic reference: it should be understood as these literal quantities in the constant pool. [may not understand, right]
Direct reference: the location (pointer, handle) where the symbol corresponding code is loaded into JVM memory.
The static linking process is completed when the class is loaded. It mainly converts some static methods. Dynamic linking is done during program running, replacing symbolic references with direct references.
1.3 initialization (class initialization clinit -- > init)
Execute the < clinit > method. The clinit method is formed by the compiler automatically collecting the assignment actions of all static variables in the class and combining the static statement blocks, also known as the class constructor method.
-
The initialization sequence is consistent with that in the source file
-
The < clinit > of the parent class will be called before the < clinit > of the child class is called
-
The JVM guarantees thread safety of the clinit method
During initialization, if a new object is instantiated, the < init > method will be called to initialize the instance variable / code block and execute the code in the corresponding construction method.
The class loading process is lazy, and it will be loaded only when it is used.
Initialization example
public class JVMTest2 { static { System.out.println("JVMTest2 Static block"); } { System.out.println("JVMTest2 Tectonic block"); } public JVMTest2() { System.out.println("JVMTest2 Construction method"); } public static void main(String[] args) { System.out.println("main method"); new Sub(); } } class Super { static { System.out.println("Super Static code block"); } public Super() { System.out.println("Super Construction method"); } { System.out.println("Super Common code block"); } } class Sub extends Super { static { System.out.println("Sub Static code block"); } public Sub() { System.out.println("Sub Construction method"); } { System.out.println("Sub Common code block"); } } JVMTest2 Static block main method Super Static code block Sub Static code block Super Common code block Super Construction method Sub Common code block Sub Construction method
To execute the main method, you do not need to create a JVMTest2 instance.
Type 2 loader
View the current JDK class loader
public class PrintJDKClassLoader { public static void main(String[] args) { ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent); ClassLoader parentParent = parent.getParent(); System.out.println(parentParent); } } // JDK8 sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@28a418fc null // JDK11 jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc jdk.internal.loader.ClassLoaders$PlatformClassLoader@1324409e null
Class 2.1 loader (JDK8)
Class loader initialization process: Java calls JVM DLL file to create a JVM, create a boot class loader (implemented by C + +), and load the extension class loader and application class loader through the JVM launcher (sun.misc.Launcher).
-
Start the class loader: it is responsible for loading the core class library under the lib directory. As a part of the JVM, it is implemented by C + +.
-
Extension class / platform class loader: it is responsible for loading JAR class packages in ext extension directory under lib directory.
-
Application class loader: it is responsible for loading the class package under the ClassPath of the user's class path, mainly loading the classes written by the user.
-
Custom class loader: it is responsible for loading the class package under the user-defined path.
By default, the JVM loads our application using the instance of the class loader AppClassLoader returned by the Launcher's getClassLoader() method.
// Launcher construction method public Launcher() { Launcher.ExtClassLoader var1; // Construct an extension class loader and set the class loader parent property to null. var1 = Launcher.ExtClassLoader.getExtClassLoader(); // Construct the application class loader and set the parent attribute of the class loader as the extended class loader. this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); Thread.currentThread().setContextClassLoader(this.loader); // Permission verification code } }
2.2 parental delegation model
The class loader adopts a three-tier, parental delegation model. The parent-child relationship of the class loader is not an inheritance relationship, but a combination relationship. Except for the startup class loader, other class loaders inherit from the ClassLoader class.
Working process: when the class loader receives a class loading request, it first determines whether the class has been loaded. If it has not been loaded, it attempts to delegate the request upward to the parent class loader for loading. When the parent class loader fails to complete the loading task, the child class loader attempts to load.
// ClassLoader protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // Check whether the class has been loaded Class<?> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { // Non boot class loader c = parent.loadClass(name, false); } else { // Start class loader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // The parent class loader cannot load the specified class } if (c == null) { // Call the findClass method of the current class loader to load the class c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }
Why does it feel like a detour to use the parental delegation model?
Under the parental delegation model, class loading requests are always delegated to the top-level startup class loader. For unloaded classes, you need to go from the bottom to the top; If the user-defined class has been loaded, the delegation process is not required.
Using the parental delegation mechanism has the following advantages:
- Sandbox security mechanism to prevent the core class library code from being tampered with.
- Avoid repeated class loading. If the parent class loader is loaded, the child class loader does not need to be loaded again.
Overall responsibility entrustment mechanism
Overall responsibility: that is, when a classloader loads a Class, other classes that the Class depends on and references are usually loaded by the classloader. Delegation mechanism: let the parent (parent) Class loader look for it first, and look for it from its own Class path only when the parent cannot be found.
Refer to Launcher construction method
Thread.currentThread().setContextClassLoader(this.loader);
Custom class loader
The user-defined class loader mainly inherits the ClassLoader class and overrides the findClass(name) method in the above source code.
public class CustomClassLoaderTest { static class CustomClassLoader extends ClassLoader { private String classFilePath; public CustomClassLoader(String classFilePath) { this.classFilePath = classFilePath; } // Load class data stream private byte[] loadClassFile(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classFilePath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException{ try { byte[] data = loadClassFile(name); // Load link initialization logic return defineClass(name,data,0,data.length); } catch (Exception e) { throw new ClassNotFoundException(); } } } public static void main(String[] args) throws Exception { CustomClassLoader classLoader = new CustomClassLoader("F:"); Class<?> clazz = classLoader.loadClass("com.lzp.java.jvm.classloader.JVMTest"); Object instance = clazz.newInstance(); Method method = clazz.getDeclaredMethod("add", null); System.out.println(method.invoke(instance)); System.out.println(clazz.getClassLoader().getClass().getName()); } }
The parent loader of the custom class loader is the application class loader. CustomClassLoader runs using AppClassLoader and is naturally a parent class loader.
Break the parental delegation mechanism
In some scenarios, breaking parental delegation is necessary. For example, there may be multiple applications in Tomcat that refer to different Spring versions. Breaking parental delegation can realize application isolation.
The JVM uses the loadClass method to implement the parental delegation mechanism. Override the loadClass method to break the parental delegation mechanism.
It is not feasible to directly delete the parent delegation code. Java code inherits from Object and always needs parent delegation to load the core code.
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null) { // Non custom classes are loaded by parental delegation if (!name.equals("com.lzp.java.jvm.classloader.JVMTest")) { c = this.getParent().loadClass(name); } else { c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }
Note: the core library code of JDK is not allowed to be configured and modified by itself. For example, you cannot use object Class copy out and execute. Sandbox isolation.