Performance optimization|Comprehensive analysis of class loading mechanism

Local code running process:

  • First compile the java file into a class file through the javac command
  • Then the virtual machine loads the class file into the memory (can come from the network)
  • The virtual machine verifies whether the class file is legal
  • Allocate memory for static variables of the class and perform zero value processing
    • Basic type (integer type: 0, boolean type: false...)
    • Reference type: set to empty
  • Resolve symbol references
    • Resolve symbolic references into direct references. Direct references refer to specific memory addresses or handles. This is a static linking process. If symbol references are resolved into direct references during runtime, it is called dynamic references.
  • initialization
    • Start to execute initialization statements in the code, including static code blocks, and assignment operations to static variables
      Insert picture description here

The following is the process of using and uninstalling.

What kind of class loaders are there in JVM

The class loader is to load the class file into the jvm.

  • Bootstrap Classloader: written in C language, responsible for loading all class files in the lib directory in the jre environment
  • Extension Classloader: load jre\lib\ext* and write all class files
  • Application Classloader: Load all the class files under the classpath, which is the code we wrote.
  • Custom class loader: load the bytecode file you need to load on demand

Verify the class files loaded by the three loaders:

    public static void main(String[] args) {
    
    
        System.out.println("bootstrapLoader加载以下文件:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
    
    
            System.out.println(urls[i]);
        }
        System.out.println();
        System.out.println("extClassloader加载以下文件:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        System.out.println("appClassLoader加载以下文件:");
        System.out.println(System.getProperty("java.class.path"));
    }

Insert picture description here

It can be seen from the figure that the class loading pair will only load the part of the class file that it is responsible for into the memory.

Class loader initialization process

We look at the contents of the launcher (Launch) construction method to find out how the class loader is initialized

    public Launcher() {
    
    
        Launcher.ExtClassLoader var1;
        try {
    
    
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
    
    
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
    
    
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
    
    
            throw new InternalError("Could not create application class loader", var9);
        }}

In the construction method, it can be seen that the system has created two loaders, namely:
ExtClassLoader and AppClassLoader, the class loader we usually use by default is to use

            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

This loader is loaded, and what we usually call the
Class.class.getClassLoader()method returns is this initialized loader

What is the relationship between these three class loaders?

We execute the following code

 public static void main(String[] args) {
    
    
        System.out.println(TestJVMClassLoader.class.getClassLoader().toString());
        System.out.println(TestJVMClassLoader.class.getClassLoader().getParent().toString());
        System.out.println(TestJVMClassLoader.class.getClassLoader().getParent().getParent());
    }

Insert picture description here

It can be found that the class loader that loads the code we created is AppClassLoader, the parent class of AppClassLoader is ExtClassLoader, and the parent class of ExtClassLoader is null. There are two class loaders here, and there is also a boot class loader. If you don’t guess wrong , It should be null, so why is it null?

Remember that we said earlier that the boot class loader is written in C language. Since it is written in C language, how can it be printed here, so we now draw a picture to sort out the relationship between these three:
Insert picture description here

What the hell is the parent delegation mechanism?

Many students have heard of this term, but there is no article that can explain clearly what is a parental delegation mechanism. Today I use what I have learned throughout my life to let the students fully understand what a parental delegation mechanism is.

What is the parent delegation mechanism?

When we look at the source code directly, it is easy to understand what is parental delegation;

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        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();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

Let me explain the execution process of this code:

  1. Use the findLoadedClass method to find the class object in the memory, if not, continue to find
  2. If parent is not empty, it refers to ExtClassLoader, the parent class loader of AppClassLoader. LoadClass is called through ExtClassLoader to load class files.
  3. If the parent is empty, it means that the current loader is already ExtClassLoader. At this time, BootstrapClass is called directly to load;
  4. If the class object has been found at this time, it can be returned directly. If it is still not found, the findCLass method implemented by the subclass loader must be called. To load our custom class file.
  5. If the subclass loader does not implement the findClass method, we can see that the default implementation of the parent class is: directly throws the classNotFound exception, generally if we customize the class loading, we only need to implement the findClass method.
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

Let’s use a picture to illustrate the process of finding "class"
Insert picture description here

In fact, the last step is to find a way by yourself, which is to implement the findclass method of the parent class.
After reading this explanation, everyone should have a general understanding of parental delegation. If you really read this process carefully, I believe you will definitely have questions:

If you need to load this bytecode, why not If you call your own findclass method directly, you have to look up level by level. Why does the JVM set up a parent delegation mechanism?

Why design a parent delegation mechanism?

  • Sandbox security mechanism: prevent the core API library from being tampered with at will
  • Avoid repeated loading of classes: when the father has already loaded the class, there is no need to load the child ClassLoader again to ensure the uniqueness of the loaded class.
    Let’s verify whether the JVM parent delegation mechanism is really effective:
    we execute the following code, classmate Let's guess what is the result of the execution?
package java.util;
public class Date {
    public static void main(String[] args) {
        System.out.println("我被执行");
    }
}

Results of the:
Insert picture description here

Why does this happen? Why can't the main method be found?
In fact, this is the parent delegation mechanism at work, because there is already a Date class with the same package name in the Java system. When we run our main method, he must first load the Date class. According to the parent delegation mechanism, AppClassLoader first asks whether the parent loader has loaded this Date. After inquiring, it is found that the parent class has already loaded this class, so AppClass should not load it again by itself, and directly use the system Date loaded by the parent loader. Class, but the system Date class does not have a main method. That's why the above error occurs.

Manually implement a class loader

package com.lezai;

import java.io.FileInputStream;
import java.lang.reflect.Method;

public class CustomClassloaderTest {
    
    
    static class CustomClassloader extends ClassLoader{
    
    
        private String classPath = "/Users/yangle/Desktop";

        /**
         * 自己实现查找字节码文件的逻辑,可以来自本地磁盘,也可以是来自网络
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
    
    
            try {
    
    
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            return null;
        }
        // 将文件读取到字节数组
        private byte[] loadByte(String name) throws Exception {
    
    
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                    + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;

        }
    }
    public static void main(String[] args) {
    
    
        CustomClassloader classLoader = new CustomClassloader();
        try {
    
    
            Class clazz = classLoader.loadClass("com.lezai.Test");
            Object obj = clazz.newInstance();
            Method method= clazz.getDeclaredMethod("out", String.class);
            method.invoke(obj,"乐哉");
            System.out.println(clazz.getClassLoader());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Implementation steps:

  • The custom class inherits the ClassLoader class
  • Rewrite the findClass method to realize the logic of finding bytecode files by yourself
  • If you don’t want to comply with the parent delegation mechanism, you can implement the loadClass method and no longer ask whether the bytecode file we need has been loaded in the parent class.

How to break the parental delegation mechanism

If we need to break the parent delegation mechanism, we only need to implement the loadClass method by ourselves, no longer ask whether the bytecode file we need has been loaded in the parent class, and then directly call findClass to load our class.

Why does tomcat break the parental delegation mechanism?

Take the Tomcat class loading as an example. If Tomcat uses the default parent delegation class loading mechanism, will it work?

  • Let's think about it: Tomcat is a web container, so what problem does it solve:
  1. A web container may need to deploy two applications. Different applications may rely on different versions of the same third-party class library. It is not required that the same class library has only one copy on the same server. Therefore, it is necessary to ensure that each application Class libraries are all independent, ensuring mutual isolation.

  2. The same version of the same class library deployed in the same web container can be shared. Otherwise, if the server has 10 applications, then 10 copies of the same class library must be loaded into the virtual machine.

  3. The web container also has its own dependent class library, which should not be confused with the application class library. Based on security considerations, the class library of the container should be isolated from the class library of the program.

  4. The web container needs to support the modification of jsp. We know that the jsp file must be compiled into a class file to run in the virtual machine. However, it is commonplace to modify the jsp after the program runs. The web container needs to support the jsp modification without restarting.

  • Let's look at our question again: Can Tomcat use the default parent delegation class loading mechanism?
    The answer is no. why?
  1. The first question is that if you use the default class loader mechanism, you cannot load two different versions of the same class library. The default class adder does not matter what version you are, and only cares about your fully qualified class name. And there is only one.

  2. The second problem is that the default class loader is achievable because its responsibility is to ensure uniqueness.

  3. The third question is the same as the first question.

  4. Let’s look at the fourth question again. We think about how we can achieve hot loading of jsp files. Jsp files are actually class files. If you modify them, but the class name is still the same, the class loader will directly take the existing method area. Yes, the modified jsp will not be reloaded. So what to do? We can directly unload the class loader of the jsp file, so you should have thought that each jsp file corresponds to a unique class loader. When a jsp file is modified, the jsp class loader is directly unloaded. Re-create the class loader and reload the jsp file.

  • The main class loaders in tomcat

    • commonLoader: Tomcat's most basic class loader, the classes in the loading path can be accessed by the Tomcat container itself and each Webapp;
    • catalinaLoader: Tomcat container private class loader, the class in the loading path is not visible to Webapp;
    • sharedLoader: The class loader shared by each Webapp, the class in the loading path is visible to all Webapps, but not to the Tomcat container;
    • WebappClassLoader: each Webapp private class loader, the class in the loading path is only visible to the current Webapp, such as loading related classes in the war package, each war package application has its own WebappClassLoader to achieve mutual isolation, such as different war package applications Introduced different spring versions, so that implementations can load their respective spring versions;
  • Diagram of the relationship between several class loaders
    Insert picture description here

Wechat search for a search [Le Zai open talk] Follow the handsome me, reply [Receive dry goods], there will be a lot of interview materials and architect must-read books waiting for you to choose, including java basics, java concurrency, microservices, middleware, etc. More information is waiting for you.

Guess you like

Origin blog.csdn.net/weixin_34311210/article/details/109232056