[toc]
Class loading mechanism
The Java VIRTUAL machine loads the data describing the Class from the Class file to the memory, verifies, transforms, and initializes the data, and finally forms Java types that can be directly used by the VIRTUAL machine. This process is called the virtual machine Class loading mechanism.
Class life cycle
From the moment a class is loaded into the virtual machine memory, to the moment it is unloaded out of memory, its whole life cycle includes: loading, validation, preparation, parsing, initialization, use, and unload these seven stages. The three parts of verification, preparation and resolution are called connection.
Load, validation, preparation, initialization and unload the order of the five stages is certain, the type of loading process must, in accordance with the order, step by step while parsing stage does not necessarily: in some cases it can start again after the initialization phase, this is in order to support the Java language runtime binding features (also called dynamic binding or late binding)
Note that the phases start in sequence, not proceed or finish in sequence, because these phases are often intermixed with each other, often invoking or activating one phase while the other is executing.
Load: Finds and loads binary data for a class
During the load phase, the virtual machine needs to do the following three things:
- 1) Get the binary byte stream that defines a class by its fully qualified name.
- 2) Convert the static storage structure represented by this byte stream to the runtime data structure of the method area.
- 3) Generate a java.lang.Class object representing the Class in memory as an access point to the various data of the Class in the method area.
Validation: To ensure the correctness of the classes being loaded
Validation is the first step in the connection phase, which ensures that the byte stream in the Class file meets the requirements of the current virtual machine and does not compromise the security of the virtual machine itself. In the verification stage, four stages of inspection actions will be roughly completed:
- File format verification: verify whether the byte stream conforms to the Class file format specification; For example, whether to use
0xCAFEBABE
Start, major and minor versions are within the scope of the current VIRTUAL machine, and constants in the constant pool are of unsupported types. - Metadata validation: Semantic analysis of the information described by bytecode (note: contrast
javac
Semantic analysis at compile time) to ensure that the information described conforms to the Requirements of the Java language specification; For example: does this class have a parent, exceptjava.lang.Object
Outside. - Bytecode validation: Determine that program semantics are legitimate and logical through data flow and control flow analysis.
- Symbol reference validation: Ensures that the parse action is performed correctly.
The validation phase is important, but not required, and has no effect on program runtime. If the referenced classes are repeatedly validated, consider using the -xVerifyNone parameter to turn off most of the class validation to shorten the virtual machine class load time.
Preparation: Allocates memory for static variables of the class and initializes them to default values
The preparation phase is the phase where memory is formally allocated for class variables and initial values are set. The memory used by these variables is allocated in the method area.
Notes for this stage:
- Only class variables (static modified variables) are allocated, not instance variables, which are allocated in the Java heap along with the object when it is instantiated.
- The initial value set here is usually the default zero value for the data type (e.g
0
,0L
,null
,false
Etc.), rather than being explicitly assigned in Java code.
For example, if a class variable is defined as: public static int value = 3; The value initial value after the preparation phase is 0, not 3, because no Java methods have been executed yet, and the PUT static instruction that assigns value to 3 is stored in the class constructor () method after the program is compiled. Therefore, assigning value to 3 will only be performed during initialization.
-
For basic data types, class variables (static) and global variables are assigned a default value of zero if they are used without an explicit assignment, while local variables must be explicitly assigned before they are used, otherwise they will fail at compile time.
-
Constants that are both static and final must be explicitly assigned at declaration, otherwise they will not pass compilation. A constant that is only final can be explicitly assigned at declaration time or at class initialization. In general, it must be explicitly assigned before use and will not be given a default zero value.
-
For reference data types, such as group references and object references, if they are directly used without explicit assignment, the system will give them the default zero value, namely NULL.
-
If you do not assign values to the elements of the array when the array is initialized, the elements will be assigned a default value of zero based on the corresponding data type.
-
If a class field has a ConstantValue property in its field property table, that is, modified by both final and static, the variable value is initialized to the value specified by the ConstValue property in the preparation phase. Public static final int value = 3; At compile time Javac will generate a ConstantValue attribute for value, and in preparation the vm will assign value to 3 based on the ConstantValue setting. Static final constants put their results into the constant pool of the calling class at compile time
Parsing: Converts symbolic references in a class to direct references
In the parsing phase, the VIRTUAL machine replaces symbolic references in the constant pool with direct references. The parsing action is mainly performed for class or interface references, fields, class methods, interface methods, method types, method handles, and call point qualifiers. A symbolic reference is a set of symbols that describe a target, which can be any literal.
A direct reference is a pointer to a target directly, a relative offset, or a handle to the target indirectly.
Initialization: Performs initialization on static variables, static code blocks of a class
Class initialization, which assigns correct initial values to static variables of a class. The JVM is responsible for initializing classes, mainly class variables. There are two ways to initialize a class variable in Java:
- Declare class variables to specify initial values
- Use static code blocks to specify initial values for class variables
Steps for class initialization
- If the class has not already been loaded and connected, the program loads and connects the class first
- If the class’s immediate parent has not already been initialized, its immediate parent is initialized first
- If there are initializers in a class, the system executes those initializers in turn
When class initialization is triggered
Initializing a class occurs only when a class is actively used, including the following six types:
-
When an object is instantiated using the new keyword.
-
Read or set a static field of a type (except for static fields that are modified by final and have been put into the constant pool at compile time).
-
When a static method of a type is called.
-
When a reflection call is made to a type using the java.lang.Reflect package’s methods, initialization needs to be triggered if the type has not already been initialized.
-
When initializing a class, if the parent class has not been initialized, the initialization of the parent class must be triggered first.
-
When the virtual machine starts, the user needs to specify a primary class (the one containing the main() method) to execute, and the virtual machine initializes this primary class first.
Class initialization is not performed in the following cases
-
A reference to a static field of a parent class by a subclass triggers initialization of the parent class, not the subclass.
-
Defining an array of objects does not trigger initialization of the class.
-
Constants are stored in the constant pool of the calling class at compile time and do not, in essence, refer directly to the class that defined the constant
Sends the class that defines the constant.
-
Fetching a Class object by its name does not trigger Class initialization.
-
If initialize is false, Class initialization will not be triggered when a specified Class is loaded via class.forname
Initialization, which tells the virtual machine whether to initialize the class.
-
Initialization is also not triggered by the default loadClass method of a ClassLoader.
use
Class to access the data structure in the method area, the object is the data in the Heap area.
uninstall
Several situations in which a Java virtual machine will end its life cycle
- The system.exit () method is executed
- The program completes normally
- The program encounters an exception or error during execution and terminates abnormally
- The Java virtual machine process was terminated due to an operating system error. Procedure
Class loader
What is a class loader
The virtual machine design team implemented the class-loading action of “getting the binary stream describing a class by its fully qualified name” outside the Java virtual machine so that the application could decide how to get the classes it needed. The code module that implements this action is called a class loader.
Class loader hierarchy
The parent delegate model requires that all class loaders have their own parent class loaders, except for the top-level start class loaders. However, the parent-child relationship between classloaders is not usually implemented in an Inheritance relationship, but usually uses Composition relationships to copy the code of the parent loader.
From the perspective of Java virtual machines, there are only two different class loaders: one is the Bootstrap ClassLoader, which is implemented in C++ and is part of the virtual machine itself; The other is all the other classloaders, which are implemented in the Java language, independent of the virtual machine, and all inherit from the abstract java.lang.classloader class.
From a Java developer’s point of view, class loaders can be more nuanced. Most Java programs use one of three system-provided class loaders:
Bootstrap ClassLoader
This class handler is responsible for storing it in the < JAVA_HOME > \lib directory, or in the path specified by the -xbootCLASspath parameter, and is recognized by the virtual machine (by filename, e.g. Rt.jar, tools.jar, Libraries with incorrect names will not be loaded even if placed in the lib directory.) Libraries are loaded into the virtual machine memory.
Extension ClassLoader
This loader is implemented by sun.misc.Launcher$ExtClassLoader, which loads all libraries in the < JAVA_HOME > \lib\ext directory, or in the path specified by the java.ext.dirs system variable. Developers can use the extended classloader directly.
Application ClassLoader
The classloader is implemented by sun.misc.Launcher$AppClassLoader. Because the application ClassLoader is the return value of the getSystem-classLoader () method in the ClassLoader class, it is also called the “system ClassLoader” in some cases.
It is responsible for loading all libraries on the user’s ClassPath, and developers can use the class loader directly in their code. If the application does not have its own custom class loader, this is generally the default class loader in the application.
All of our applications are loaded by a combination of these three types of loaders, including our own class loaders if necessary.
There are three ways to load classes
- The command line startup application is initialized by the JVM
- Dynamically loaded with the class.forname () method
- Load dynamically through the classloader.loadClass () method
Code examples:
public class loaderTest {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = HelloWorld.class.getClassLoader();
System.out.println(loader);
// Use classloader.loadClass () to load classes without performing initialization blocks
loader.loadClass("Test2");
// Use class.forname () to load the Class, which executes the initialization block by default
// Class.forName("Test2");
// Use class.forname () to load the Class and specify a ClassLoader. Static blocks are not executed when initialized
// Class.forName("Test2", false, loader);}}public class Test2 {
static {
System.out.println("Static initialization block executed!"); }}Copy the code
Class.forname () and classLoader.loadClass ()
-
Class.forname (): In addition to loading the Class’s.class file into the JVM, the Class is interpreted, executing the static block of the Class;
-
Classloader.loadclass (): does only one thing: loads a. Class file into the JVM. Static blocks are executed only at newInstance.
-
Class. ForName (name, initialize, loader) functions with parameters can also control whether static blocks are loaded. And only the newInstance() method is invoked to create objects of the class using the call constructor.
JVM class loading mechanism
Overall responsible for
When a Class is loaded by a Class loader, other classes that that Class depends on and references are also loaded by the Class loader, unless it is shown to be loaded using another Class loader.
Commissioned by the parent class
Let the parent class loader try to load the class first, and only try to load the class from its own classpath if the parent class loader cannot load the class.
Caching mechanisms
The caching mechanism will ensure that all loaded classes will be cached. When a program needs to use a Class, the Class loader first looks for the Class in the cache. Only when 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 Class changes are made, the JVM must be restarted for the program changes to take effect.
Parent delegation mechanism
The working process of the parental delegation model is: If a classloader receives a classload request, it does not try to load the class itself at first. Instead, it delegates the request to the parent classloader. This is true at every level of classloaders, so all load requests should eventually be sent to the top level of the starting classloader. Only when the parent loader reports that it cannot complete the load request (it did not find the desired class in its search scope) will the child loader attempt to complete the load itself.
Examples are as follows:
- When an AppClassLoader loads a class, it first does not attempt to load the class itself. Instead, it delegates the classloading request to the parent class loader, ExtClassLoader.
- When ExtClassLoader loads a class, it doesn’t try to load the class itself in the first place. Instead, it delegates the class loading request to BootStrapClassLoader.
- If the BootStrapClassLoader fails to load (for example, the class cannot be found in $JAVA_HOME/jre/lib), the ExtClassLoader will be used to try to load the class.
- If the ExtClassLoader also fails to load, AppClassLoader is used to load it. If the AppClassLoader also fails to load, ClassNotFoundException is reported.
Class parent delegate mechanism
This hierarchical relationship between class loaders, shown in the figure above, is called the Parents Delegation Model of class loaders. The parent delegate model requires that all class loaders have their own parent class loaders, except for the top-level start class loaders. The parent-child relationship between class loaders is not typically implemented in an Inheritance relationship, but in a Composition relationship that replicates the code of the parent loader.
The parent-delegate model of class loaders was introduced during JDK1.2 and is widely used in almost all Java programs since then, but it is not a mandatory constraint model, but rather one of the loader implementations recommended by Java designers to developers.
The working process of the parental delegation model is: If a classloader receives a classload request, it does not try to load the class itself at first. Instead, it delegates the request to the parent classloader. This is true at every level of classloaders, so all load requests should eventually be sent to the top level of the starting classloader. Only when the parent loader reports that it cannot complete the load request (it did not find the desired class in its search scope) will the child loader attempt to complete the load itself.
One of the obvious benefits of using the parent-delegate model to organize relationships between class loaders is that Java classes have a hierarchical relationship with priority along with their classloaders. For example, the java.lang.Object class, which is stored in rt.jar, is ultimately delegated to the boot class loader at the top of the model by any classloader. Therefore, the Object class is the same class in the various classloader environments of the program. Instead of using the parent delegate model and loading by class loaders, if a user writes a class called java.lang.Object and places it in the program’s ClassPath, multiple Object classes will appear. The most basic behavior in the Java type system is not guaranteed, and the application becomes a mess.
Advantages of parental delegation
- System classes prevent multiple copies of the same bytecode from appearing in memory
- Ensure the safe and stable running of Java programs
Parent delegate code implementation
publicClass<? > loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronizedClass<? > loadClass(String name,boolean resolve)throws ClassNotFoundException {
// First check if the type is already loaded
Class c = findLoadedClass(name);
if (c == null) {
// If it is not loaded, delegate to the parent class loader or delegate to the start class loader
try {
if(parent ! =null) {
// Delegate to the parent class loader if there is one
c = parent.loadClass(name, false);
} else {
// If there is no parent Class loader, check whether the Class was loaded by the bootloader, by calling the native Class findBootstrapClass(String name).c = findBootstrapClass0(name); }}catch (ClassNotFoundException e) {
// Call its own loading function if neither the parent class loader nor the launcher class loader can complete the loading taskc = findClass(name); }}if (resolve) {
resolveClass(c);
}
return c;
}
Copy the code
The logic of this code is clear: first check to see if the requested load type has already been loaded. If not, the parent loadClass() method is called. If the parent is empty, the startup class loader is used as the parent by default. If the parent class loader fails to load and throws a ClassNotFoundException, call your own findClass() method to try to load.
Custom class loaders
Normally, we use the system class loader directly. However, there are times when custom class loaders are also needed. For example, an application transmits Java class bytecodes over the network. To ensure security, the bytecodes are encrypted. In this case, the system class loader cannot load the bytecodes. Custom classloaders are generally inherited from classloaders, so we just need to override the findClass method.
Here’s an example to illustrate the process of customizing a class loader:
The core of the custom class loader is the retrieval of the bytecode file, which needs to be decrypted in the class if it is encrypted bytecode. Since this is just a demonstration, I have not encrypted the class file, so there is no decryption.
public class MyClassLoader extends ClassLoader {
private String root;
protectedClass<? > 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(a) {
return root;
}
public void setRoot(String root) {
this.root = root;
}
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
classLoader.setRoot("D:\\temp"); Class<? > testClass =null;
try {
testClass = classLoader.loadClass("com.pdai.jvm.classloader.Test2");
Object object = testClass.newInstance();
System.out.println(object.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch(IllegalAccessException e) { e.printStackTrace(); }}}Copy the code
Here are a few things to note:
- The filename passed here needs to be the full qualified name of the class, i.e
com.pdai.jvm.classloader.Test2
Format, because the defineClass method is processed in that format. - It is best not to override the loadClass method, as this can easily break the parent delegate pattern.
- This kind of Test class itself can be AppClassLoader class loading, so we can’t put the com/pdai/JVM/this/Test2 class under the class path. Otherwise, the parent delegate mechanism will cause the class to be loaded by the AppClassLoader rather than by our custom class loader.