banner
jzman

jzman

Coding、思考、自觉。
github

Virtual Machine Class Loading Mechanism

Today, let's introduce the JVM class loading mechanism. The main contents are as follows:

  1. Overview
  2. Timing of class loading
  3. Process of class loading
  4. Class loaders
  5. Classification of class loaders
  6. Delegation model

Overview#

JVM loads the bytecode (.class) files into memory, verifies, resolves, and initializes the data, and finally generates Java types that can be directly used by the JVM. This is the class loading mechanism of JVM.

In Java, the loading, linking, and initialization of various types are completed during program execution. This approach incurs some performance overhead during class loading, but it has high flexibility. The dynamic extension feature of Java relies on dynamic loading and dynamic linking during runtime. For example, in plugin technology, resources are loaded and replaced using custom class loaders, which rely on the runtime class loading feature of Java.

Timing of class loading#

From the moment a class is loaded into the JVM memory until it is unloaded from the JVM memory, the lifecycle of class loading is as shown in the following figure:

image

The order of the five stages: loading, verification, preparation, initialization, and unloading is fixed, but the resolution of the class is not necessarily performed after initialization. This is to support the runtime binding of the Java language. In the entire process of class loading, each stage is triggered by the previous stage.

The JVM specification defines the initialization stage of a class, but there are no constraints on the loading stage, which is specifically controlled by the JVM itself. However, loading, verification, and preparation must be completed before the initialization stage.

So when does a class start initialization? The JVM strictly specifies the following situations where a class must be initialized:

  1. When encountering the new, getstatic/putstatic, invokestatic instructions, if the class has not been initialized, it needs to be initialized. The above instructions correspond to object instantiation using the new keyword, reading or setting a static property, and calling a static method, respectively. You can verify this by using the javap command to view the implementation of the bytecode file.
  2. When using java.lang.reflect to reflectively invoke a class, if the class has not been initialized, it needs to be initialized.
  3. When initializing a class, if its parent class has not been initialized, the parent class is initialized first.
  4. When the JVM starts, the main class specified by the user, such as the class with the main method, is initialized first.
  5. When using the dynamic language support of JDK 1.7, if the final resolution result of the java.lang.invoke.MethodHandler instance is REF_getStatic, REF_putStatic, REF_invokeStatic, and the classes corresponding to these handles have not been initialized, they need to be initialized. MethodHandler can be understood as another form of reflection.

Process of class loading#

The following explains the specific stages of class loading.

Loading#

The class file loads the bytecode content into memory through the class loader, converts this static data into runtime data structures in the method area, and generates the java.lang.Class object corresponding to the class file in the heap memory. This Class object is the entry point for accessing class data in the method area.

The JVM specification does not specify the source of class files. Examples are as follows:

  • Obtained from a zip package, eventually becoming the basis for jar and war formats.
  • Obtained from the network, typical applications are applets.
  • Generated at runtime, typical applications are dynamic proxy technology, where ProxyGenerator.generatrProxyClass is used in java.lang.reflect.Proxy to generate binary bytecode streams for specific interfaces.
  • Generated from other files, obtained from a database, etc.

The loading stage of a class and the subsequent linking stage are performed in an interleaved manner, without clear boundaries. The loading stage may not be completed, and the linking stage may have already started. However, the start time of the two stages still maintains a fixed order.

Linking#

Linking includes verification, preparation, and resolution.

  • Verification: Ensures that the information contained in the bytecode stream of the class file meets the requirements of the current virtual machine and does not harm the security of the virtual machine itself. From an overall perspective, the verification stage mainly includes file format verification, metadata verification, bytecode verification, and symbol reference verification. The specific verification content can be checked in the Java Virtual Machine Specification.
  • Preparation: Allocates memory for class variables and sets the initial values of class variables. The initial value is generally the initial value of the data type, not the value initialized in the actual code. For example, the initial value of int is 0. The memory used by these class variables is allocated in the method area. Class variables refer to variables modified by the static keyword.
  • Resolution: The JVM replaces the symbol references in the constant pool with direct references. The symbol references here refer to the symbol references mentioned in the symbol reference verification in the previous verification stage.

Initialization#

The initialization stage of a class is the last step of the class loading stage. Except for user-defined class loaders, all other operations in the loading and linking stages are performed by the JVM itself. The initialization stage is when Java code, that is, bytecode, is actually executed. Regarding class initialization, you can understand the following points:

  1. The initialization stage is the process of executing the class constructor () method.
  2. The () method is automatically generated by the compiler by collecting all variable assignment actions of class variables and statements in static blocks static{}. The order of collection by the compiler is consistent with the order of statements in the source code. For example, variables defined after a static block can only be assigned but not accessed in the static block.
  3. When initializing a class, if the parent class has not been initialized, the parent class is initialized first.
  4. The JVM ensures that the () method of a class is correctly locked and synchronized in a multi-threaded environment.
  5. When accessing a static field of a Java class, only the class that actually declares the field is initialized.

Class loaders#

As the name suggests, a class loader is used to load Java classes into the JVM. All class loaders are instances of the java.lang.ClassLoader class. As mentioned earlier, class files are loaded by class loaders during the class loading stage. That is, the binary bytecode stream defining a class can be obtained by using the fully qualified name of the class. This action is implemented by the class loader.

For any class, its uniqueness in the JVM is established together with its class loader. Each class loader has its own independent class name space, which means that two identical classes loaded by different class loaders will no longer be equal.

Classification of class loaders#

From the perspective of the JVM, there are only two different class loaders:

  1. Bootstrap ClassLoader: Generally implemented in C++, specifically implemented by the JVM.
  2. Other class loaders: Implemented in Java, independent of the JVM, and all are instances of java.lang.ClassLoader, such as DexClassLoader in Android.

From the perspective of Java developers, class loaders can be divided into three categories:

  1. Bootstrap ClassLoader: Responsible for loading the class libraries under JAVA_HOME\lib or the paths specified by the -Xbootclasspath parameter, and the libraries recognized by the JVM (recognized only by file name, such as rt.jar). The bootstrap class loader cannot be directly used by JAVA programs.
  2. Extension ClassLoader: This class loader is implemented by sun.misc.Launcher$ExtClassLoader and is responsible for loading classes under JAVA_HOME\lib\ext or the paths specified by the java.ext.dirs system variable. The extension class loader can be used directly.
  3. Application ClassLoader: This class loader is implemented by sun.misc.Launcher$AppClassLoader. It is the return value of the getSystemClassLoader() method in ClassLoader. It is generally referred to as the system class loader. It is responsible for loading the classes specified in the user class path (ClassPath). Developers can directly use this class loader. If the application does not have its own custom class loader, this is the default class loader in the program.

Delegation model#

Let's take a look at the relationship between the class loaders shown above:

image

The hierarchical relationship between class loaders shown in the above figure is called the delegation model of class loaders. The delegation model of class loaders requires that except for the top-level bootstrap class loader, all other class loaders should have their own parent class loaders. The parent-child relationship between class loaders is generally not implemented through inheritance, but through composition to reuse the code of the parent loader. This approach is not a mandatory constraint model, but a recommended class loader implementation method given by Java designers.

So what is the workflow of the delegation model?

When a class loader receives a class loading request, it does not load the class directly, but delegates the class loading request to its parent class loader. This process continues until each class loading request is delegated to the bootstrap class loader. Only when the parent class loader cannot complete the class loading, will the child class loader try to load it by itself. The loading process is as follows:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
        // 1. Check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    // 2. If it has not been loaded, call the parent class loader to load it
                    c = parent.loadClass(name, false);
                } else {
                    // 3. If the parent class loader does not exist, use the bootstrap class loader to load it directly
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                // 4. If neither the parent class loader nor the bootstrap class loader has loaded the class, call its own findClass method to load the class
                c = findClass(name);
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

Since JDK 1.2, the java.lang.ClassLoader has added a new protected method findClass(), so if you want to customize a class loader, you can directly implement the findClass() method instead of overriding the loadClass() method. Because the loadClass() method ultimately calls the findClass() method, this way the custom class loader complies with the rules of the delegation model.

The JVM class loading mechanism and the related knowledge of class loaders have been introduced. Class loaders well support the dynamic extension feature of Java. They are also used in Android, such as PathClassLoader and DexClassLoader used in plugin technology, which are indirectly subclasses of ClassLoader. It is based on the lack of restrictions on the source of class files that App pluginization can be achieved.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.