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

The process of class loading

In the Java language, type loading, concatenation, and initialization are done during program execution

When a type is loaded into vm memory and unloaded from memory, Its life cycle will go through Loading, Verification, Preparation, Resolution, Initialization, Using, and Verification 2. Unloading, such as validation, preparation and parsing, is called Linking.

loading

During the load 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 port for the Class’s various data in the method area.

After the loading phase, the binary byte streams outside the Java virtual machine are stored in the method area in the format specified by the virtual machine.

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.

Stages of loading and the connection part of the action, such as part of the bytecode file format validation action) was performed by cross loading phase is not yet complete, may have begun connection phase, but the clip in the middle of the stage of loading action, still belongs to the part of connection, the two stages of start time remains fixed order.

validation

Validation is the first step in the connection phase, which aims to ensure that the information contained in the byte stream of a Class file complies with the constraints of the Java Virtual Machine Specification. The verification phase will roughly complete the following four stages of verification: file format verification, metadata verification, bytecode verification and symbol reference verification.

File format validation

Verify that the byte stream complies with the Class file format specification and can be processed by the current version of the virtual machine. This phase may include the following verification points:

  • Does it start with the magic number 0xCAFEBABE?

  • Check whether the major and minor versions are acceptable for the current Java VM.

  • If there are unsupported constant types in the constant pool (check the constant tag flag).

  • Is there any index value that points to a constant that does not exist or that does not conform to a type?

  • CONSTANT_UTF8_INFO whether there is data in the constant that does not conform to utF-8 encoding.

  • Whether any other information has been deleted or added to parts of the Class file and the file itself.

Metadata validation

Semantic analysis of the information described by bytecode to ensure that the information described conforms to the Requirements of the Java Language Specification may include the following verification points:

  • Whether this class has a parent (all classes except java.lang.Object should have a parent).

  • Whether the parent of this class inherits classes that are not allowed to be inherited (classes modified by final).

  • If the class is not abstract, does it implement all the methods required by its parent or interface?

  • Whether a field or method ina class conflicts with the parent class (for example, overwriting a final field in the parent class, or a method overloading that does not conform to rules, such as method arguments that are identical but return value types that are different).

Bytecode verification

The main purpose is to determine that the program semantics are legal and logical through data flow analysis and control flow analysis. In this stage, the method body of the Class (Code attribute in the Class file) is verified and analyzed to ensure that the methods of the verified Class will not endanger the virtual machine security during running, for example:

  • Ensure that the data type of the operand stack and the sequence of instruction codes work together at any time. For example, there is no such thing as “putting an int on the operand stack and loading it into the local variable table as long”.

  • Ensure that no jump instruction jumps to a bytecode instruction outside the method body.

  • Ensure that type conversions in the method body are always valid. For example, you can assign a subclass object to a parent data type, but you are not allowed to assign a parent object to a subclass data type, or even assign an object that has no inheritance relationship to it.

Bytecode verificationSymbolic reference verification

The validation behavior in the last phase occurs when the virtual machine converts symbolic references to direct references, which occurs in the parse phase, the third phase of the connection.

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. In this phase, the following items are usually verified:

  • Whether a class can be found for a fully qualified name described by a string in a symbol reference.

  • Whether a field descriptor for a method and methods and fields described by a simple name exist in the specified class.

  • The accessibility (private, protected, public) of classes, fields, and methods in symbolic references is accessible to the current class.

Notation to refer to the main purpose of the validation is to ensure that the parsing behavior can execute in normal, if not through reference symbol verification, the Java virtual machine will be thrown a Java lang. IncompatibleClassChangeError subclass exception, typical such as:

  • java.lang.IllegalAccessError
  • java.lang.NoSuchFieldError
  • Java. Lang. NoSuchMethodError, etc.

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). The initial value referred to here is “normally” the zero value of the data type.

public static int value = 123;
Copy the code

The value variable has an initial value of 0 instead of 123 after the preparation phase, because no Java methods have been executed yet. The putStatic instruction that assigns value to 123 is stored in the class constructor () method after the program is compiled. So assigning value to 123 is not performed until the initialization phase of the class

A zero value for the base data type

public static final int value = 123;
Copy the code

If a class field has a ConstantValue attribute in its field attribute table, the variable value is initialized to the initial value specified by the ConstantValue attribute in the preparation phase, assigning value to 123.

parsing

The parsing phase is the process by which the Java virtual machine replaces symbolic references in the constant pool with direct references

  • Symbolic References: Symbolic References describe the referenced target as a set of symbols, which can be any literal, as long as they are used to unambiguously locate the target.
  • Direct References: A Direct reference is a pointer that can point directly to the target, a relative offset, or a handle that can be indirectly located to the target.

Initialize the

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 say this in a more direct form:

The initialization phase is the process of executing the class constructor clinit() method.

The Clinit () method is generated by combining the assignment actions of all the compiler’s class variables with statements in the static{} block. The order in which the compiler collects the statements is determined by the order in which the statements appear in the source file. The static block can only access variables defined before the static block and those defined after it. The previous static block can be assigned, but not accessed

Unlike class constructors, the Clinit () method does not explicitly call the parent constructor. The Java virtual machine guarantees that the clinit() method is executed first, which means that static blocks defined in the parent class take precedence over variable assignments in the subclass. So the first clinit() method to be executed in the Java virtual machine must be of type java.lang.object.

The Clinit () method is not required for a class or interface, and the compiler may not generate the Clinit () method for a class that has no static blocks and no assignment to variables.

Static blocks cannot be used in the interface, but there is still assignment for variable initialization, and the interface generates the Clinit () method as well. But unlike classes, the clinit() method that executes the interface does not need to execute the parent interface’s Clinit () method 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 clinit() method of the interface when initialized.

The Java virtual machine must ensure that a class’s Clinit () methods are locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one thread will execute the class’s Clinit () methods, and all the other threads will block and wait until the active thread finishes executing the clinit() methods. If you have a lengthy operation in a class’s Clinit () method, multiple processes can block.

Class loader

The class-loading action of “get the binary stream describing a class by its fully qualified name” is implemented outside the Java virtual machine so that the application can decide how to get the required classes. The code that does this is called a Class Loader.

For any one Class, must by the Class loader to load it with the Class itself established its uniqueness in the Java virtual machine, compare whether the two classes “equal”, only in these two classes are from the same Class loader under the premise of load to be meaningful, otherwise, even if these two classes derived from the same Class files, Being loaded by the same Java virtual machine, the two classes must not be equal as long as they are loaded by different classloaders.

Java three-tier class loader

Bootstrap Class Loader

Is responsible for loading files that are stored in

\lib, or in a path specified by the -xbootclasspath parameter, and 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.

The boot class loader cannot be directly referenced by Java programs. If you need to delegate the loading request to the boot class loader when writing a custom class loader, you can use NULL instead

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.

Application Class Loader

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.” It is responsible for loading all libraries on the user’s ClassPath. If the application does not have its own custom class loader, this is generally the default class loader in the application.

Parental delegation model

The hierarchical relationship between the various class loaders is called the “Parent 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. 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.

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.

For example, the java.lang.Object class, which is stored in rt.jar, is delegated to the boot class loader at the top of the model by any class loader. Therefore, Object can be guaranteed to be the same class in each class loader environment of the program. On the other hand, instead of using the parent delegate model and having each class loader load it by itself, if the user wrote a class named java.lang.Object and put it in the ClassPath, the system would have multiple Object classes and the application would be cluttered.

// Implement 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

The example analysis

Tomcat: Orthodox classloader architecture

In the Tomcat directory structure, you can set 3 groups of directories (/common/*, /server/*, and /shared/*) to store Java libraries. You should also add the Web application’s own “/ WEB-INF /*” directory, four groups in all. Place the Java class libraries in these four groups of directories, each with its own distinct meaning:

  • Place it in the /common directory. The class library can be used by Tomcat and all Web applications.

  • Place it in the /server directory. The class library can be used by Tomcat and is invisible to all Web applications.

  • Place it in the /shared directory. The class library is shared by all Web applications, but not visible to Tomcat itself.

  • Put it in the /WebApp/WEB-INF directory. The class library can only be used by the Web application and is not visible to Tomcat or other Web applications.

To support this directory structure, and to load and isolate the libraries in the directory, Tomcat custom class loaders are implemented according to the classic parent-delegate model

Common class loader, Catalina class loader (also known as Server class loader), Shared class loader, and Webapp class loader are class loaders defined by Tomcat itself. They add Java libraries in /common/*, /server/*, /shared/*, and /WebApp/WEB-INF/*, respectively.

There are usually multiple instances of WebApp class loader and JSP class loader. Each Web application corresponds to a WebApp class loader, and each JSP file corresponds to a JasperLoader.

Classes that can be loaded by the Common class loader can be used by the Catalina and Shared class loaders, while classes that can be loaded by the Catalina and Shared class loaders are isolated from each other. WebApp classloaders can use classes loaded by Shared classloaders, but individual WebApp classloaders are isolated from each other.

JasperLoader can only load the Class file compiled by the JSP file, which exists to be discarded: When the server detects that the JSP file has been modified, it replaces the current JasperLoader instance and implements HotSwap for the JSP file by creating a new JSP classloader.

Classloaders are an innovation of the Java language and one of the important reasons for the rapid popularity of the early Java language.

reference

Understanding the Java Virtual Machine in Depth: Advanced JVM Features and Best Practices (version 3)