JVM series (1): loading mechanism of java classes

1. What is class loading

Class loading refers to reading the binary data in the .class file of the class 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 for Encapsulates the data structure of the class within the method area. The final product of class loading is the Class object located in the heap area. 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.

 

The class loader does not need to wait until a class is "actively used for the first time" to load it, the JVM specification allows the class loader to pre-load a class in anticipation that it will be used, if encountered during the pre-loading process. The .class file is missing or has an error, the class loader must report an error when the program actively uses the class for the first time (LinkageError error) If the class has not been actively used by the program, the class loader will not report an error

 
How to load .class files
– Load directly from local system
– Download .class files over the network
- Load .class files from archives like zip, jar, etc.
– Extract .class files from proprietary database
– Dynamically compile Java source files into .class files
 

2, the life cycle of the class

 

The class loading process includes five stages: loading, verification, preparation, parsing, and initialization. Among the five phases, the order in which the four phases of loading, verification, preparation and initialization occur is determined, while the parsing phase is not necessarily, it can start after the initialization phase in some cases, this is to support Java Runtime binding of the language (also known as dynamic binding or late binding). Also note that the phases here are started sequentially, not performed or completed sequentially, as these phases are usually intermixed with each other, usually invoking or activating one phase while another is being executed.

• Load: Find and load the binary data of the class

   The first stage of the class loading process at load time, during the loading stage, the virtual machine needs to do three things:

    1. Obtain the binary byte stream defined by the fully qualified name of a class.

    2. Convert the static storage structure represented by this byte stream into the runtime data structure of the method area.

    3. Generate a java.lang.Class object representing this class in the Java heap as the access entry to the data in the method area.

    Compared with other stages of class loading, the loading stage (to be precise, the action of obtaining the binary byte stream of the class in the loading stage) is the most controllable stage, because developers can use the class loading provided by the system. You can use the class loader to complete the loading, or you can customize your own class loader to complete the loading.

    After the loading phase is completed, the binary byte stream outside the virtual machine is stored in the method area according to the format required by the virtual machine, and an object of the java.lang.Class class is also created in the Java heap, so that the Objects access these data in the method area.

•connect

 – Validation: Ensure the correctness of loaded classes

Verification 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 the Class file meets the requirements of the current virtual machine and will not compromise the security of the virtual machine itself. The verification phase will roughly complete four stages of inspection actions:

File format verification: Verify whether the byte stream conforms to the specification of the 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, whether the constants in the constant pool have unsupported types .

Metadata verification: perform semantic analysis on the information described by the bytecode (note: compare the semantic analysis of the javac compilation phase) to ensure that the information described meets the requirements of the Java language specification; for example: whether this class has a parent class, except java outside of .lang.Object.

Bytecode Verification: Through data flow and control flow analysis, it is determined that the program semantics are legal and logical.

Symbolic reference validation: Ensures that resolution actions are performed correctly.

The verification phase is very important, but not necessary. It has no effect on the program runtime. If the referenced class has been repeatedly verified, you can consider using the -Xverifynone parameter to turn off most of the class verification measures to shorten the virtual machine class. load time.

 

 – Prepare: allocate memory for static variables of the class and initialize them to default values

   The preparation phase is the phase in which memory is formally allocated for class variables and initial values ​​of class variables are set, all of which will be allocated in the method area. There are a few things to note about this stage:

    1. At this time, memory allocation only includes class variables (static), not instance variables. Instance variables will be allocated in the Java heap along with the object when the object is instantiated.

    2. The initial value set here is usually the default zero value of the data type (such as 0, 0L, null, false, etc.), rather than the value explicitly assigned in the Java code.

   Suppose a class variable is defined as: public static int value = 3;

   Then the initial value of the variable value after the preparation phase is 0, not 3, because no Java method has been executed yet, and the putstatic instruction that assigns value to 3 is stored in the class constructor <clinit> after the program is compiled. () method, so the action of assigning value to 3 will not be executed until the initialization phase.

 
· The following points should also be noted here:
· For basic data types, for class variables (static) and global variables, if they are used directly without explicitly assigning them, the system will assign them a default zero value, while for local variables, when using It must be explicitly assigned a value before it, otherwise it will not pass at compile time.
· For constants modified by both static and final, they must be explicitly assigned at the time of declaration, otherwise they will not pass at compile time; while the constants only modified by final can be explicitly assigned at the time of declaration, You can also explicitly assign a value to it when the class is initialized. In short, it must be explicitly assigned before use, and the system will not assign a default zero value to it.
· For the reference data type reference, such as array reference, object reference, etc., if it is used directly without explicit assignment, the system will assign it the default zero value, that is, null.
· If no value is assigned to each element in the array when the array is initialized, the elements in it will be assigned the default zero value according to the corresponding data type.
 

    3. If the ConstantValue attribute exists in the field attribute table of the class field, that is, it is modified by both final and static, then the variable value will be initialized to the value specified by the ConstValue attribute in the preparation stage.

   Suppose the above class variable value is defined as: public static final int value = 3;

   When compiling, Javac will generate the ConstantValue property for the value. In the preparation stage, the virtual machine will assign the value to 3 according to the setting of ConstantValue. This is the case, recalling the second example of passive references to objects in the previous blog post. We can understand that the static final constant puts its result into the constant pool of the class that calls it at compile time

 

 – Parse: Convert symbolic references in classes to direct references

The parsing phase is a process in which the virtual machine replaces the symbolic references in the constant pool with direct references. The parsing action is mainly performed for 7 types of symbolic references, including classes or interfaces, fields, class methods, interface methods, method types, method handles, and call site qualifiers. A symbolic reference is a set of symbols to describe a target, which can be any literal.

A direct reference is a pointer directly to the target, a relative offset, or a handle that is located indirectly to the target.

•initialization

 Initialization, assigning correct initial values ​​to the static variables of the class, the JVM is responsible for initializing the class, mainly initializing the class variables. There are two ways to initialize class variables in Java:

  ① Declare a class variable to specify the initial value

  ②Use static code blocks to specify initial values ​​for class variables

 JVM initialization steps

 1. If the class has not been loaded and connected, the program will first load and connect the class

 2. If the direct parent class of the class has not been initialized, initialize its direct parent class first

 3. If there are initialization statements in the class, the system executes these initialization statements in turn

Class initialization timing: Only when the class is actively used will the class be initialized. The active use of the class includes the following six types:

– Create an instance of a class, that is, the new way

– Access a static variable of a class or interface, or assign a value to the static variable

– call a static method of the class

– Reflection (eg Class.forName("com.shengsiyuan.Test"))

– When a subclass of a class is initialized, its parent class will also be initialized

– The class marked as the startup class (Java Test) when the Java virtual machine starts, directly use the java.exe command to run a main class

 

end life cycle

• In the following cases, the Java virtual machine will end its life cycle

– Executed the System.exit() method

– Program execution ends normally

– The program encounters an exception or error during execution and terminates abnormally

– Java virtual machine process terminated due to operating system error

3. Class loader

Looking for a class loader, let's start with a small example

 
package com.neo.classloader;
public class ClassLoaderTest {
     public static void main(String[] args) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        System.out.println(loader);
        System.out.println(loader.getParent());
        System.out.println(loader.getParent().getParent());
    }
}
 

After running, the output result:

It can be seen from the above results that the parent Loader of ExtClassLoader has not been obtained. The reason is that Bootstrap Loader (bootstrap class loader) is implemented in C language and cannot find a definite way to return the parent Loader, so it returns null .

The hierarchical relationship of these class loaders is shown in the following figure:

 

Note: The parent class loader is not implemented by inheritance, but by composition.

From the perspective of the Java virtual machine, there are only two different class loaders: the startup class loader: it is implemented in C++ (this is limited to Hotspot, which is the default virtual machine after JDK1.5, there are many other The virtual machine is implemented in the Java language), which is part of the virtual machine itself; all other class loaders: these class loaders are implemented in the Java language, independent of the virtual machine, and all inherit from the abstract class java.lang .ClassLoader, these class loaders need to be loaded into memory by the startup class loader before they can load other classes.

From a Java developer's point of view, class loaders can be roughly divided into the following three categories:

Startup class loader: Bootstrap ClassLoader, responsible for loading the class library stored in JDK\jre\lib (JDK represents the installation directory of JDK, the same below), or in the path specified by the -Xbootclasspath parameter, and can be recognized by the virtual machine (Such as rt.jar, all classes starting with java.* are loaded by Bootstrap ClassLoader). The startup class loader cannot be directly referenced by a Java program.

Extension class loader: Extension ClassLoader, which is implemented by sun.misc.Launcher$ExtClassLoader, which is responsible for loading all classes in the DK\jre\lib\ext directory or the path specified by the java.ext.dirs system variable Libraries (such as classes starting with javax.*), developers can directly use the extension class loader.

Application class loader: Application ClassLoader, this class loader is implemented by sun.misc.Launcher$AppClassLoader, which is responsible for loading the classes specified by the user class path (ClassPath), developers can use this class loader directly, if the application The program has not customized its own class loader. In general, this is the default class loader in the program.

The application is loaded by the cooperation of these three class loaders. If necessary, we can also add custom class loaders. Because the ClassLoader that comes with the JVM only knows how to load standard java class files from the local file system, if you write your own ClassLoader, you can do the following:

1) Automatically verify digital signatures before executing untrusted code.

2) Dynamically create custom build classes that meet user-specific needs.

3) Get the java class from a specific place, such as the database and the network.

 

JVM 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 another class loader is explicitly used to load

• Parent class delegation, let the parent class loader try to load the class first, and only try to load the class from its own classpath when the parent class loader cannot load the class

•Cache mechanism, the cache mechanism will ensure that all loaded classes will be cached. When a class needs to be used in the program, the class loader will first look for the class from the cache area, and the system will read it only if the cache area does not exist. The binary data corresponding to the class is converted into a Class object and stored in the buffer area. This is why after modifying the Class, the JVM must be restarted for the program modification to take effect.

4. Class loading

There are three ways of class loading:

1. When the command line starts the application, it is initialized and loaded by the JVM

2. Dynamic loading through the Class.forName() method

3. Dynamic loading through the ClassLoader.loadClass() method

example:

 
package com.neo.classloader;
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 the class, the initialization block will not be executed
                loader.loadClass("Test2");
                //Use Class.forName() to load the class, the initialization block will be executed by default
//                Class.forName("Test2");
                //Use Class.forName() to load the class and specify the ClassLoader, the static block is not executed during initialization
//                Class.forName("Test2", false, loader);
        }
}
 

demo type

 
public class Test2 {
        static {
                System.out.println("Static initialization block executed!");
        }
}
 

Switching the loading method respectively will have different output results.

 

Class.forName()和ClassLoader.loadClass()区别

Class.forName(): In addition to loading the .class file of the class into the jvm, it will also interpret the class and execute the static block in the class;

ClassLoader.loadClass(): Only do one thing, that is, load the .class file into the jvm, and will not execute the content in static. Only in newInstance will the static block be executed.

Note:

The Class.forName(name, initialize, loader) function with parameters can also control whether to load the static block. And only the newInstance() method is called by calling the constructor to create an object of the class.

5. Parental delegation model

The workflow of the parent delegation model is: if a class loader receives a class loading request, it will not try to load the class by itself first, but delegate the request to the parent loader to complete it, and so on. Therefore, all the Class loading requests should eventually be passed to the top-level startup class loader, and only if the parent loader does not find the required class in its search scope, i.e. the loading cannot be completed, the child loader will try to do it on its own. Load the class.

Parental delegation mechanism:

1. When AppClassLoader loads a class, it will not try to load the class by itself first, but delegate the class loading request to the parent class loader ExtClassLoader to complete.

2. When ExtClassLoader loads a class, it will not try to load the class first, but delegates the class loading request to BootStrapClassLoader to complete.

3. If BootStrapClassLoader fails to load (for example, the class is not found in $JAVA_HOME/jre/lib), ExtClassLoader will be used to try to load;

4. If ExtClassLoader also fails to load, AppClassLoader will be used to load it. If AppClassLoader also fails to load, an exception ClassNotFoundException will be reported.

 

ClassLoader source code analysis:

 
public Class<?> loadClass(String name)throws ClassNotFoundException {
            return loadClass(name, false);
    }
    
    protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
            // First determine whether the type has been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                //If not loaded, delegate to the parent class to load or delegate to the startup class loader to load
                try {
                    if (parent != null) {
                         //If there is a parent class loader, delegate to the parent class loader to load
                        c = parent.loadClass(name, false);
                    } else {
                    //If there is no parent class loader, check whether it is a class loaded by the boot class loader, by calling the native method native Class findBootstrapClass(String name)
                        c = findBootstrapClass0(name);
                    }
                } catch (ClassNotFoundException e) {
                 // If neither the parent class loader nor the startup class loader can complete the loading task, call its own loading function
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
 

The meaning of the parent delegation model:

- The system class prevents multiple copies of the same bytecode from appearing in memory

- Ensure safe and stable operation of Java programs

6. Custom class loader

    Usually, we use the system class loader directly. However, sometimes, we also need custom class loaders. For example, the application transmits the bytecode of Java classes through the network. In order to ensure security, these bytecodes have been encrypted. At this time, the system class loader cannot load them, so a custom class loader is required to accomplish. Custom class loaders are generally inherited from the ClassLoader class. From the analysis of the loadClass method above, we only need to rewrite the findClass method. Below we use an example to demonstrate the process of customizing the class loader:

 
package com.neo.classloader;

import java.io. *;


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();
        classLoader.setRoot("E:\\temp");

        Class<?> testClass = null;
        try {
            testClass = classLoader.loadClass("com.neo.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 ();
        }
    }
}
 

The core of the custom class loader is to obtain the bytecode file. If it is encrypted bytecode, 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. There are a few things to note here:

1. The file name passed here needs to be the fully qualified name of the class, that is, in the format of com.paddx.test.classloading.Test, because the defineClass method is processed in this format.

2. It is best not to rewrite the loadClass method, because it is easy to destroy the parent delegation mode.

3. This type of Test class itself can be loaded by the AppClassLoader class, so we cannot put com/paddx/test/classloading/Test.class on the classpath. Otherwise, due to the existence of the parent delegation mechanism, the class will be loaded directly by AppClassLoader instead of by our custom class loader.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324868093&siteId=291194637