Learning Java Virtual Machines
- Learn Java Virtual Machine part 1: Memory area and garbage collection
- Java Virtual Machine (2) : Class file structure
- Introduction to the Bytecode Instructions
preface
Understanding the JVM is a basic requirement for Java programmers, but how many students like me are so obsessed with solving the bug heap layout that they forget the internal discipline and have a fragmentary understanding of the JVM? A systematic study of the JVM may take us further down the road.
Class life cycle
When a type is loaded into vm memory and unloaded from memory, 2. Its life cycle must go through Loading, Verification, Preparation, Resolution, Initialization, Using and Unloading. The three parts of validation, preparation and parsing are collectively referred to as Linking.
The process of class loading
loading
Loading is a phase in Class Loading. In this phase, the Java VIRTUAL machine needs to do three things:
- Gets the binary byte stream that defines a class by its fully qualified name.
- Transform the static storage structure represented by this byte stream into the runtime data structure of the method area.
- Generate a java.lang.Class object in memory that represents the Class and acts as an access point for the Class’s various data in the method area
After the loading phase is complete, the external binary byte streams of the Java VM are stored in the method area in the format set by the VM. The data storage format in the method area is fully defined by the VM. The Java Virtual Machine Specification does not specify the specific data structure in this area. Once the type data is properly placed in the method area, an object of the Java.lang. Class Class is instantiated in the Java heap memory, which acts as an external interface for the program to access the type data in the method area
validation
Validation is the first step in the connection phase. The purpose of this phase is to ensure that the information contained in the byte stream of a Class file complies with all the constraints of the Java Virtual Machine Specification and that the information can be run as code without compromising the security of the virtual machine itself. In 2011, The Java Virtual Machine Specification (Java SE version 7) was published, which greatly increased the description of the verification process. In the verification stage, the following four verification actions will be roughly completed: file format verification, metadata verification, bytecode verification and symbol reference verification.
1. Verify the file format
The first step is to verify that the byte stream complies with the Class file format specification and can be processed by the current version of the VIRTUAL machine.
2. Verify metadata
The main purpose of the second stage is to perform semantic verification on the metadata information of the class to ensure that there is no metadata information contrary to the definition of the Java Language Specification.
3. Bytecode verification
The third stage is the most complicated one in the whole validation process. The main purpose is to determine the program semantics is legal and logical through data flow analysis and control flow analysis. After verifying the data types in the metadata information in the second stage, the method body of the Class (Code attribute in the Class file) should be verified and analyzed to ensure that the methods of the verified Class will not endanger the security of virtual machines when running.
4. Symbol reference verification
The verification behavior in the last stage occurs when the virtual machine converts symbolic references to direct references [3]. This transformation takes place in the third stage of the connection, the parsing phase. Symbolic reference verification can be regarded as the matching verification of all kinds of information outside the class itself (various symbolic references in the constant pool). In plain English, that is, whether the class is missing or denied access to some external classes, methods, fields and other resources on which it depends.
To prepare
The preparation phase is the phase where you formally allocate memory and set initial values for variables defined in a class (that is, static variables, modified by static). There are two other confusing concepts I need to emphasize about the preparation phase. The first is that only class variables, not instance variables, will be allocated in the Java heap along with the object when it is instantiated. The second is that the initial value here is “normally” zero for the data type.
public static int value = 123;
For example, the initial value of value after the preparation phase is 0 instead of 123. The putStatic instruction that assigns value to 123 is stored in the class constructor () method after the program is compiled, so the assignment of value to 123 is not executed until the initialization phase of the class.
public static final int value = 123;
In the case of the ConstantValue property, the value of the variable is initialized to the initial value specified by the ConstantValue property in the preparation phase.
parsing
The parsing phase is the process by which the Java virtual machine replaces symbolic references in the constant pool with direct references.
1. Symbolic References:
A symbolic reference describes the referenced object as a set of symbols, which can be any literal, as long as they are used to unambiguously locate the object. Symbolic references are independent of the memory layout implemented by the virtual machine, and the target of the reference is not necessarily something that has been loaded into the virtual machine’s memory. The memory layout of various virtual machine implementations can vary, but the symbolic references they accept must all be consistent, because the literal form of the implementation symbolic references is explicitly defined in the Java Virtual Machine Specification’s Class file format.
2. Direct References:
A direct reference is a pointer that can point directly to a target, a relative offset, or a handle that can be indirectly located to the target. A direct reference is directly related to the memory layout implemented by the VIRTUAL machine. The direct reference translated from the same symbolic reference on different virtual machine instances will not be the same. If there is a direct reference, the target of the reference must already exist in the virtual machine’s memory.
Initialize the
The initialization phase of a class is the last step in the class loading process. In addition to the user application’s partial participation in the loading phase through custom class loaders, the rest of the class loading actions are completely controlled by the Java VIRTUAL machine. It is not until the initialization phase that the Java virtual machine actually begins to execute the Java program code written in the class, handing control to the application.
During the preparation phase, variables have already been assigned the initial zero value required by the system, while during the initialization phase, class variables and other resources are initialized according to a subjective plan made by the programmer through the program code. We can also express this in a more direct way: the initialization phase is the process of executing the class constructor
() method. < Clinit >() is not a method written directly by programmers in Java code; it is an automatic artifact of the Javac compiler.
<clinit>()
Method is by the compiler automatically collect all kinds of variable assignment in class action and static blocks (static {} block) of the statement in merger, the compiler collection order is decided by the order of the statement in the source file, static block can only access to the definition in the static block before the variable, defined in the variable after it, The previous static block can be assigned, but not accessed.
Public class Test {static {I = 0; Print (I); print(I); print(I); } static int I = 1; }Copy the code
<clinit>()
Method, unlike class constructors (that is, instance constructor () methods in the virtual machine perspective), does not require an explicit call to the superclass constructor, and the Java virtual machine guarantees that the () method of the superclass is executed before the () method of the subclass is executed. So the first () method to be executed in the Java virtual machine must be of type java.lang.object.- Because of the parent class
<clinit>()
Method is executed first, which means that the static block defined in the parent class takes precedence over the assignment of variables in the child class.
static class Parent { public static int A = 1; static { A = 2; } } static class Sub extends Parent { public static int B = A; } public static void main(String[] args) { System.out.println(Sub.B); // output 2}Copy the code
<clinit>()
Methods are not necessary for a class or interface, and if a class has no static block and no assignment to a variable, the compiler may not generate a method for that class<clinit>()
Methods.- Static blocks cannot be used in interfaces, but there is still assignment for variable initialization, so interfaces are generated as well as classes
<clinit>()
Methods. But unlike classes, the () method of an interface does not need to execute the () method of the parent interface first, because the parent interface is initialized only when a variable defined in the parent interface is used. In addition, the implementation class of the interface does not execute the interface’s () method when initialized. - The Java virtual machine must ensure that a class’s () methods are locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one of them will execute the class’s () methods, and all the other threads will block and wait until the active thread finishes executing the () method. A long operation in a class’s () method can cause multiple process blocks, which are often hidden in practice
Static class DeadLoopClass {static {// If no if statement is added, If (true) {system.out.println (thread.currentThread () + "init" DeadLoopClass"); while (true) { } } } } public static void main(String[] args) { Runnable script = new Runnable() { public void run() { System.out.println(Thread.currentThread() + "start"); DeadLoopClass dlc = new DeadLoopClass(); System.out.println(Thread.currentThread() + " run over"); }}; Thread thread1 = new Thread(script); Thread thread2 = new Thread(script); thread1.start(); thread2.start(); }Copy the code
Class loader
Comparing two classes to be “equal” only makes sense if they are loaded by the same classloader. Otherwise, even if two classes come from the same Class file and are loaded by the same Java virtual machine, as long as they are loaded by different classloaders, the two classes must not be equal.
Parental delegation model
1. Bootstrap Class Loader:
This class loader is responsible for loading files stored in <JAVA_HOME>\lib, or in the path specified by the -xbootclasspath parameter, that are recognized by the Java 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.) The libraries are loaded into the virtual machine memory.
2. Extension Class Loader:
The classloader is implemented as Java code in the sun.misc.Launcher$ExtClassLoader class. It is responsible for loading all libraries in the <JAVA_HOME>\lib\ext directory, or in the path specified by the java.ext.dirs system variable.
3. Application Class Loader:
Because the application ClassLoader is the return value of the getSystemClassLoader() 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
Parents delegate the working process of the model
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.
The implementation of the parental delegate model
protected synchronized Class<? > loadClass(String name, Boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name); if (c == null) { try { if (parent ! = null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); }} Catch (ClassNotFoundException e) {// If (c == null) {// If (c == null) {// C = findClass(name); c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }Copy the code