java class loading mechanism
What is class loading?
Class loading refers to reading the binary data in the class's. Class file into memory, placing it in the method area of the runtime data area, and then creating a java.lang.Class object in the heap area [that is, the class object we use in mapping] to encapsulate the data structure of the class in the method area. The final product of class loading is the class object located in the heap. The class object encapsulates the data structure of the class in the method area and provides Java programmers with an interface to access the data structure in the method area. [in short: the Java virtual machine loads the data describing the class from the class file into memory, verifies, converts, parses and initializes the data, and finally forms a Java type that can be directly used by the virtual machine. This is the loading mechanism of the virtual machine.]
The class loader does not need to wait until a class is "actively used for the first time". The JVM specification allows the class loader to pre load a class when it is expected to be used. If the. Class file is missing or there is an error during the pre loading process, the class loader must report the error (LinkageError error error) when the program actively uses the class for the first time If the class has not been actively used by the program, the class loader will not report an error.
Class loading process
First, the premise of class loading is. Class file, not. java file. The process from java file to class file is called compilation process.
JDK/JRE/JVM
JDK 8 is a superset of JRE 8, which contains all the contents of JRE 8, compilers, debuggers and other development applets and applications. JRE 8 provides libraries, Java virtual machines (JVMs), and other component languages that run applets and applications written in Java programming. Note that JRE contains specifications for components not required by Java SE, including standard and non-standard Java components.
The Java virtual machine loads the data describing the Class from the Class file into memory, verifies, converts, parses and initializes the data, and finally forms a Java type that can be directly used by the virtual machine, which is the loading mechanism of the virtual machine.
java file compilation process
The compilation process can be roughly divided into the following steps:
- Written java file, Person.java
- Lexical analyzer
- tokens stream
- Parser
- Syntax tree / abstract syntax tree
- semantic analyzer
- Annotation abstract syntax tree
- Bytecode generator
- Finally, get the. class file
A Class file is a set of binary streams based on 8-bit bytes. The first four bytes of each Class file are called Magic Number. Its only function is to determine whether the file is a Calss file that can be accepted by the virtual machine. That is, the four bytes CA FE BA BE. Similar to identity
Class loading process diagram
The JVM divides the class loading process into three major steps: loading, linking, and initializing. The link is divided into three steps: verification, preparation and parsing.
The process of class loading includes five stages: loading, verification, preparation, parsing and initialization. In these five phases, the sequence of loading, validation, preparation and initialization is determined, while the parsing phase is not necessarily. In some cases, it can start after the initialization phase, which is to support the runtime binding of the Java language (also known as dynamic binding or late binding). Also note_ The stages here start in sequence, not in sequence, Because these stages are usually intermixed, they usually call or activate another stage in the execution of a phase.
Class loading
-
Find and load the compiled binary stream. class file
-
In the class loading phase, the virtual machine needs to complete three things:
- Get the binary byte stream defined by a class through its fully qualified name [find binary stream file with fully qualified name]
- Convert the static storage structure represented by this byte stream into the runtime data structure of the method [load data into memory]
- Generate a java.lang.Class object representing this class in the Java heap as the access entry to these data in the method area [java reflection]
be careful:
- In the loading phase, the action of obtaining the binary byte stream of the class is completed through the class loader. We can use the class loader provided by the system or customize our own class loader to complete the class loading. [at this stage, we can do many things for developers, such as creating hook tasks]
- The loaders provided by the system are: ClassLoader
Link
The link is divided into three parts: verification, preparation and analysis
- Verify [ensure the correctness of loaded classes]
Ensure that the information contained in the byte stream of the Class file meets the requirements of the current virtual machine and will not endanger the security of the virtual machine itself. It mainly includes four kinds of verification, file format verification, metadata verification, bytecode verification and symbol reference verification
-
File format verification: verify whether the byte stream conforms to the specification of Class file format; For example: whether it starts with 0xCAFEBABE, whether the major and minor version numbers are within the processing range of the current virtual machine, and whether the constants in the constant pool have unsupported types.
-
Metadata verification: perform semantic analysis on the information described by bytecode (Note: compare the semantic analysis in javac compilation stage) to ensure that the information described meets the requirements of Java language specification; For example, whether this class has a parent class, except java.lang.Object.
-
Bytecode verification: through the analysis of data flow and control flow, it is determined that the program semantics is legal and logical.
-
Symbol reference verification: ensure that the parsing action can be executed correctly.
Note: the verification phase is very important, but not necessary. It has no impact on the program running time. If the referenced class has been verified repeatedly, you can consider using the - Xverifynone parameter to close most class verification measures to shorten the virtual machine class loading time.
- Prepare [allocate memory for the static variable of the class and initialize it to the default value]
The preparation stage is the stage of formally allocating memory for class variables and setting the initial value of class variables. These memory will be allocated in the method area. Note:
- The memory allocated at this stage only includes Static variables decorated with Static, which are in the jvm method area
For example:
Suppose a class variable is defined as: public static int value = 3;
Then the initial value of the variable value after the preparation stage is 0 instead of 3, because no Java method has been executed at this time, and the putstatic instruction that assigns value to 3 is stored in the < clinit > () method of the class constructor after the program is compiled. Therefore, the action that assigns value to 3 will be executed in the initialization stage.
-
If the class field exists in the field property sheet`ConstantValue`Property, i.e**Simultaneously by final and static Modified variable**,So in the preparation phase, the variables value Will be initialized to ConstValue Property.
- Resolution [the process of replacing symbol references in constant pool with direct references]
The parsing stage is a process in which the virtual machine replaces the symbol reference in the constant pool with a direct reference. The parsing action is mainly for class or interface, field, class method, interface method, method type, method handle and call point qualifier. Symbol reference is a set of symbols to describe the target, which can be any literal quantity.
A direct reference is a pointer directly to the target, a relative offset, or a handle indirectly located to the target.
Initialize
Initialization: give the correct initial value to the static variables of the class. The JVM is responsible for initializing the class, mainly initializing the class variables
JVM initialization steps
- If the class has not been loaded and connected, the program loads and connects the class first
- If the direct parent of this class has not been initialized, initialize its direct parent first
- If there are initialization statements in the class, the system executes these initialization statements in turn
Class initialization timing: class initialization will occur only when the class is actively used
-
When new, getstatic, putstatic and invokestatic are encountered, if the class has not been initialized, its initialization needs to be triggered first.
The most common Java code scenarios for generating these four instructions are:
- When instantiating an object with the new keyword
- When reading or setting static fields of a class (except those modified by final and put into the constant pool by the compiler)
- When calling a static method of a class
-
When you use the methods of the java.lang.reflect package to make reflection calls to a class, if the class has not been initialized, you need to trigger its initialization first.
-
When initializing a class, if you find that its parent class has not been initialized, you need to trigger the initialization of its parent class first.
-
When the virtual machine starts, the user needs to specify a main class to be executed (the class containing the main() method), and the virtual machine initializes the main class first.
-
When JDK 1.7 dynamic language support is used, if a java.lang.invoke.MethodHandle instance is used, the final parsing result is Ref_ getstatic,REF_ putstatic,REF_ If the method handle of invokestatic and the class corresponding to this method handle is not initialized, its initialization needs to be triggered first out.
End life cycle [Java virtual machine will end life cycle]
-
Execute the System.exit() method
-
End of normal program execution
-
The program encountered an exception or error during execution and terminated abnormally
-
The Java virtual machine process terminated due to an error in the operating system
ClassLoader
The JVM class loader is used to load the bytecode content of the class file into memory, convert these static data into the runtime data structure in the method area, and generate a java.lang.Class object representing this class in the heap as the access entry to the class data in the method area. Class loader is completed by ClassLoader and its subclasses [start class loader is written by C, others are written by Java itself, independent of the virtual machine, and all inherit ClassLoader]. The hierarchical relationship and loading order of classes can be described in the following figure:
- Start class loader [Bootstrap ClassLoader]: responsible for loading $Java_ All the jar packages specified by the class or - xbootclassooth option in jre/lib/rt.jar in home. Implemented by C + +, not a subclass of ClassLoader. The startup class loader cannot be directly referenced by Java programs
- Extension ClassLoader: implemented by sun.misc.Launcher$ExtClassLoader, it is responsible for loading all class libraries (such as classes starting with javax. *) in the JDK\jre\lib\ext directory or in the path specified by the java.ext.dirs system variable. Developers can directly use the Extension ClassLoader.
- Application ClassLoader: implemented by sun.misc.Launcher$AppClassLoader, it is responsible for loading the jar package specified in classpath and the classes and jar packages in the directory specified by java.class.path
- Custom class loader [User ClassLoader]: load classes through the subclass of java.lang.ClassLoader, which belongs to the ClassLoader customized by the application according to its own needs. For example, tomcat and jboss will implement the ClassLoader according to the j2ee specification
The JVM's own ClassLoader only knows how to load standard java class files from the local file system. Therefore, if you write your own ClassLoader, you can do the following:
-
The digital signature is automatically verified before executing the untrusted code.
-
Dynamically create customized build classes that meet user specific needs.
-
Get Java classes from specific places, such as databases and networks.
Class loading principle
Check whether a class has been loaded: check from bottom to top, from Custom ClassLoader to BootStrap ClassLoader layer by layer. As long as a classloader has been loaded, it is regarded as loaded. Ensure that this class is loaded only once for all classloaders. Loading order: the loading order is top-down, that is, the upper layer tries to load this class layer by layer.
Class loading mechanism
-
Overall responsibility: when a Class loader is responsible for loading a Class, other classes that the Class depends on and references will also be loaded by the Class loader, unless it is shown that another Class loader is used for loading
-
Parent class delegate: first let the parent class loader try to load the class. Only when the parent class loader cannot load the class, it tries to load the class from its own class path
-
Caching mechanism: the caching mechanism will ensure that all loaded classes will be cached. When a Class needs to be used in the program, the Class loader first looks for the Class from the cache. Only if the cache does not exist, the system will read the binary data corresponding to the Class, convert it into a Class object and store it in the cache. This is why after modifying the Class, the JVM must be restarted for the program modification to take effect
Gets the loader currently used by java
public static void main(String[] args) { //new object User1 user1 = new User1(); //Through the getClass method, getClassLoader can get the same reflection of the class loader System.out.println("Gets the class loader for the current object " + user1.getClass().getClassLoader()); System.out.println("Gets the parent class loader of the class loader of the current object " + user1.getClass().getClassLoader().getParent()); System.out.println(user1.getClass().getClassLoader().getParent().getParent()); /** * Output results: * Gets the class loader sun.misc.launcher for the current object$ AppClassLoader@18b4aac2 * Gets the parent class loader sun.misc.launcher of the class loader of the current object$ ExtClassLoader@2f2c9b19 * null // Bootstrap Loader(The boot class loader) is implemented in C + + language and cannot be obtained by java * */ }
Parental delegation mechanism
Define source code analysis
definition:
When a class loader receives a class loading request, it will not try to load the class by itself, but will pass it up to the highest level, that is, BootStrapClassLoader. When the parent class fails to load the class, it will pass it down to try to load the class.
Summary: the request will be passed from bottom to top, and the loading class action will be tried from top to bottom.
Advantages:
- A Java class has a hierarchical relationship with priority along with the class loader that loads it. For example, the Object class in Java is stored in rt.jar. No matter which class loader wants to load this class, it is finally delegated to the startup class loader at the top of the model for loading. Therefore, Object is the same class in various class loading environments. If the two parent delegation model is not adopted, and each class loader loads it by itself, there will be many different Object classes in the system.
- The system class prevents multiple copies of the same bytecode from appearing in memory to ensure the safe and stable operation of Java programs
jdk1.8 source code
Loadclass of ClassLoader (string name, Boolean resolve)
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded //First, check whether the class has been loaded Class<?> c = findLoadedClass(name); if (c == null) {// Equal to null, not loaded long t0 = System.nanoTime(); try { if (parent != null) {//Is the parent loader empty c = parent.loadClass(name, false);//Parent loader exists, load with parent loader } else {//If its parent class loader is null, it means that this class loader is an extension class loader, and the parent class loader is a startup class loader. Try to use bootstrap classloader to load classes c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { //If c is empty, the parent loader fails to load // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name);//Try loading using a custom class loader // 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) {//Whether to initialize the class is controlled by the passed in ID resolveClass(c); } return c; } }
Custom class loader
Usually, we use the system class loader directly. However, sometimes we also need to customize the class loader. For example, applications transmit Java class bytecodes through the network. In order to ensure security, these bytecodes are encrypted. At this time, the system class loader cannot load them, so it needs to be implemented by custom class loader. Custom class loaders generally inherit from the ClassLoader class. From the above analysis of the loadClass method, we only need to override the findClass method. Let's use an example to demonstrate the process of customizing the class loader:
package com.zy.gm.classLoader; import java.io.*; /** * @author Administrator * @date 2021/11/29 14:23 **/ public class MyClassLoader extends ClassLoader{ private String root; protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = loadClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { 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 classLoader = new MyClassLoader(); //Disk absolute address if the loaded file is not placed in the class directory, you need to add the absolute address prefix // classLoader.setRoot("D:\\idea-develop-project\\Project_All\\springboot-dome\\src\\main\\java\\"); Class<?> testClass = null; try { //The package address of the file to be loaded testClass = classLoader.loadClass("com.zy.gm.orika.User1"); Object object = testClass.newInstance(); System.out.println("object = " + object); //Loader used System.out.println(object.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. Since this is just a demonstration, I did not encrypt the class file, so there is no decryption process. Here are a few points to note:
-
The file name passed here needs to be the fully qualified name of the class, that is, in the format of com.zy.gm.orika.User1, because the defineClass method is processed in this format.
-
It is better not to override the loadClass method, because it is easy to break the parent delegation mode.
Actual combat analysis
Example 1
class School { static { System.out.println("School Static code block"); } } class Teacher extends School { static { System.out.println("Teacher Static code block"); } public static String name = "Tony"; public Teacher() { System.out.println("I'm Teacher"); } } class Student extends Teacher { static { System.out.println("Student Static code block"); } public Student() { System.out.println("I'm Student"); } } class InitializationDemo { public static void main(String[] args) { System.out.println("Teacher's name: " + Student.name); //entrance } }
Output results:
School Static code block Teacher Static code block Teacher's name: Tony
You can see that there is no "Student static code block" in the output
reason:
For a static field, only the class that directly defines the field will be initialized (execute the static code block). Therefore, referencing the static field defined in the parent class through its subclass will only trigger the initialization of the parent class, not the subclass.
In other words, the class loader does not initialize students. We only initialize schools and teachers through the name field of teachers called by students
Class loading process:
- First, the program goes to the main method and uses the standardized output of the name class member variable in the Student class, but this class member variable is not defined in the Student class. So we went to the parent class and found the corresponding class member variable in the Teacher class, which triggered the initialization of Teacher.
- However, because the Teacher class inherits the school class, you need to initialize the school class first and then the Teacher class. So we first initialize the school class output: School static code block, and then initialize the Teacher class output: Teacher static code block.
- Finally, after all parent classes are initialized, the Student class can call the static variable of the parent class to output: Teacher's name Tony
Example 2
public class classloader01 { public static void main(String[] args) { new Student(); } } class School { static { System.out.println("School Static code block"); } public School() { System.out.println("I'm School"); } } class Teacher extends School { static { System.out.println("Teacher Static code block"); } public Teacher() { System.out.println("I'm Teacher"); } } class Student extends Teacher { static { System.out.println("Student Static code block"); } public Student() { System.out.println("I'm Student"); } } //Output results: /** School Static code block Teacher Static code block Student Static code block I'm School I'm Teacher I'm Student **/
analysis:
- Instantiate a Student object
- Trigger Student parent object initialization and output static code blocks in the parent object: School static code block, Teacher static code block and Student static code block
- Class initialization is completed, and the constructor method is called, while the constructor method of Student class will also drive the constructor methods of Teacher and School classes to call and output I'm School, I'm Teacher and I'm Student
Example 3*
class Teacher { public static void main(String[] args) { staticFunction(); } static Teacher teacher = new Teacher(); static { System.out.println("teacher Static code block"); } { System.out.println("teacher Common code block"); } Teacher() { System.out.println("teacher Construction method"); System.out.println("age= " + age + ",name= " + name); } public static void staticFunction() { System.out.println("teacher Static method"); } int age = 24; static String name = "Tony"; /** * teacher Common code block * teacher Construction method * age= 24,name= null * teacher Static code block * teacher Static method * */ }
Class loading process analysis
- When the JVM is in the preparation phase, it allocates memory and initializes class variables. At this point, our teacher instance variable is initialized to null, and the name variable is initialized to null
- After entering the initialization phase, because the Teacher() method is the entry of the program, initialize the main class of the main method to initialize the teacher
- The JVM initializes the Teacher class by first executing the class constructor (collecting all static code blocks and class variable assignment statements in the class in order to form the class constructor), and then executing the object constructor (collecting member variable assignment first, then collecting ordinary code blocks, finally collecting the object constructor, and finally forming the object constructor).
First, collect all static code blocks and class variables in code order for assignment, that is, execute the following code and initialize name to null
static Teacher teacher = new Teacher(); static { System.out.println("teacher Static code block"); } static String name = "Tony"; // The static variable preparation stage only allocates memory and does not initialize. It is null during initialization
Here, the constructor of the object is triggered (first collect the assignment of member variables, then collect common code blocks, finally collect the object constructor, and finally form the object constructor), so as to execute:
int age = 24; //Assign age to 24 { System.out.println("teacher Common code block"); } Teacher() { System.out.println("teacher Construction method"); //At this time, name has not been assigned, so it is null System.out.println("age= " + age + ",name= " + name); }
Final execution
static { System.out.println("teacher Static code block"); } //Now perform the operation of assigning name to Tony public static void staticFunction() { System.out.println("teacher Static method"); }
summary
- Determines the initial value of the class variable. In the preparation stage of class loading, the JVM initializes the zero value for the class variable. At this time, the class variable will have an initial zero value. If it is a class variable modified by final, it will be directly initialized to the value desired by the user.
- Initialize the entry method. After entering the initialization phase of class loading, the JVM will look for the entire main method entry to initialize the entire class where the main method is located. When a class needs to be initialized, the class constructor will be initialized first, and then the object constructor will be initialized.
- Initializes the class constructor. Initializing a class constructor is the first step in initializing a class. It will collect the assignment statements and static code blocks of class variables in order, and finally form a class constructor, which is executed by the JVM.
- Initializes the object constructor. Initializing the object constructor is the second step after the class constructor is executed. It will collect code in the order that the execution class member becomes an assignment, a common code block, and an object construction method, and finally form an object constructor, which is finally executed by the JVM.
- If you encounter the initialization of other classes when initializing the class where the main method is located, continue to initialize in the order of initializing the class constructor and initializing the object constructor. This cycle is repeated, and finally the class where the main method is located is returned.
reference resources: