An article explains Tomcat's class loading mechanism

An article explains Tomcat's class loading mechanism

- Preface -

Do you understand the class loading mechanism of Apache Tomcat? This article will start from the underlying principles and thoroughly reveal the source code, mechanisms, and solutions involved in Tomcat class loading, helping you to deeply master the core of Tomcat class loading!

- JVM Class Loader -

1. JVM class loader

Speaking of Tomcat class loader, we have to briefly talk about JVM class loader, as shown in the following figure:

  • Bootstrap ClassLoader: It is used to load the basic running classes provided by JVM, that is, the core class library located in the %JAVA_HOME%/jre/lib directory;
  • Extension ClassLoader: Extension ClassLoader, a standard extension mechanism provided by Java, is used to load Jar packages other than core class libraries. That is, as long as the Jar is copied to the specified extension directory (can be multiple), the JVM will automatically load it (no need to specify it through -classpath). The default extension directory is %JAVA_HOME% plus e/lib/ext. A typical application scenario is that Java uses this class loader to load Jars that are provided by default by the JVM but do not belong to the core class library. It is not recommended to place the class libraries that the application depends on in the extension directory, because the class libraries in this directory are visible to all applications running based on the JVM;
  • Application ClassLoader: Application ClassLoader is used to load the Jar package in the directory specified by the environment variable CLASSPATH (not recommended) or the -classpath runtime parameter. The System class loader is usually used to load application Jar packages and their startup entry classes (Tomcat's Bootstrap class is loaded by the System class loader).

The working principle of these class loaders is the same, the difference is that their loading paths are different, that is, the paths searched by the findClass method are different.

The parent delegation mechanism is to ensure that a Java class is unique in the JVM. If you accidentally write a class with the same name as a JRE core class, such as the Object class, the parent delegation mechanism can ensure that the Object class in the JRE is loaded instead of the Object class you wrote.

This is because when AppClassLoader loads your Object class, it will delegate to ExtClassLoader to load, and ExtClassLoader will delegate to BootstrapClassLoader. BootstrapClassLoader finds that it has already loaded the Object class and will return directly without loading the Object class you wrote.

Please note here that the parent-child relationship of class loaders is not implemented through inheritance. For example, AppClassLoader is not a subclass of ExtClassLoader, but the parent member variable of AppClassLoader points to the ExtClassLoader object. By the same token, if you want to customize the class loader, do not inherit AppClassLoader, but inherit the ClassLoader abstract class, and then rewrite the findClass and loadClass methods. Tomcat implements its own class loading logic through a custom class loader. I don't know if you have noticed that if you want to break the parent delegation mechanism, you need to rewrite the loadClass method, because the default implementation of loadClass is the parent delegation mechanism.

2. Source code of class loader

public abstract class ClassLoader {
  // Each class loader has a parent loader private final ClassLoader parent;
  public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
     protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
           // If not loaded if (c == null) {
                if (parent != null) {
                  // First delegate to the parent loader to load, note that this is a recursive call c = parent.loadClass(name, false);
                } else {
                 // If the parent loader is empty, check if the Bootstrap loader has been loaded c = findBootstrapClassOrNull(name);
                }
              
            // If the parent loader fails to load, call its own findClass to load if (c == null) {        
                    c = findClass(name);
                }
            } 
        
            return c;
        }
        
    }
    //The findClass method in ClassLoader needs to be overridden by the subclass. The following code is the corresponding code protected Class<?> findClass(String name){
       //1. According to the passed class name, search for the class file in a specific directory and read the .class file into memory...
       //2. Call defineClass to convert the byte array into a Class object return defineClass(buf, off, len);
    }
      // Parse the bytecode array into a Class object and implement it with native methods protected final Class<?> defineClass(byte[] b, int off, int len){
    
    }
    
}

Our custom class loader needs to rewrite the loadClass method of ClassLoader.

- Tomcat's class loading mechanism -

1. Characteristics of loading mechanism

Isolation: Web application libraries are isolated from each other to prevent dependent libraries or application packages from affecting each other. Imagine that if we have two web applications, one using Spring 2.5 and the other using Spring 4.0, and the application server uses one class loader to load, then the web application will fail to start successfully due to Jar package overwriting;

Flexibility: Since the class loaders between web applications are independent of each other, we can redeploy only one web application, and the class loader of the web application will be recreated without affecting other web applications. If you use a class loader, this is obviously not possible, because when there is only one class loader, the dependencies between classes are disorganized, and it is impossible to completely remove the classes of a web application.

Performance: Since each Web application has a class loader, the Web application does not search for Jar packages contained in other Web applications when loading classes. The performance is naturally higher than when the application server has only one class loader.

2. Tomcat class loading solution

  • The roles of the bootstrap class loader and the extension class loader remain unchanged;
  • The system class loader normally loads classes under CLASSPATH, but the Tomcat startup script does not use this variable. Instead, it loads the class that Tomcat starts, such as bootstrap.jar, which is usually specified in catalina.bat or catalina.sh. Located in CATALINA_HOME/bin;
  • Common class loader loads some classes commonly used by Tomcat and applications, located in CATALINA_HOME/lib, such as servlet-api.jar;
  • Catalina ClassLoader is used to load visible classes inside the server. These classes cannot be accessed by applications.
  • SharedClassLoader is used to load application shared classes, which are not dependent on the class server;
  • WebappClassLoader, each application will have a unique Webapp ClassLoader, which is used to load the classes under /WEB-INF/classes and /WEB-INF/lib of this application.

Tomcat 8.5 changed the strict parent delegation mechanism by default:

  • Load from cache;
  • If there is no such file in the cache, ExtClassLoader will be called first to load it. The extension class loader follows parent delegation. It will call bootstrap to check whether the corresponding lib exists, and then fall back to ExtClassLoader to load the data under the extension package.
  • If not loaded, load from /WEB-INF/classes;
  • If not loaded, load it from /WEB-INF/lib/*.jar. If not loaded, WebAppclassLoader will delegate to SharedClassLoader, SharedClassLoader will delegate to CommonClassLoader..., and then delegate to BootstrapClassLoader. Then BootstrapClassLoader searches for the corresponding class in its own directory. If it is found, it will load it. If not, it will delegate to the next level ExtClassLoader. ExtClassLoader then searches for the class in its own directory. If it is found, it will load it. If not, it will delegate to the next level... follow the parent delegation principle.

3. Analyze the loading process of the application class loader

The application class loader is WebappClassLoader, and its loadClass is in its parent class WebappClassLoaderBase.

  public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;
            // Log access to stopped class loader
            checkStateForClassLoading(name);    
            //Load the class from the local cache of the current ClassLoader and return if found clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
            // If there is no local cache, call the findLoadedClass method of ClassLoader to check whether the jvm has loaded this class. If it has, return directly.
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
            String resourceName = binaryNameToPath(name, false);
            //At this time, javaseClassLoader is the extension class loader, and the extension class loader is assigned to javaseClassLoader
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
              .....
            //If it can be obtained using getResource //If it can be obtained using getResource of the extension class loader, it proves that it can be loaded by the extension class loader. Next, arrange for the extension class loader to load if (tryLoadingFromJavaseLoader) {
                try {
                    //Use the extended class loader to load clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // (0.5) Permission to access this class when using a SecurityManager
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        log.info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }
            boolean delegateLoad = delegate || filter(name, true);
            // (1) Delegate to our parent if requested
            //If true, use the parent class loader to load if (delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug(" Delegating to parent classloader1 " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug(" Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // (2) Search local repositories
            if (log.isDebugEnabled())
                log.debug("Searching local repositories");
            try {
                // Load locally clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug(" Loading class from local repository");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
            // (3) Delegate to parent unconditionally
            //It still hasn't loaded yet. Try to load it again using the parent class loader if (!delegateLoad) {
                    if (log.isDebugEnabled())
                    log.debug(" Delegating to parent classloader at end: " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug(" Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }
        throw new ClassNotFoundException(name);
    }

Note: In the 37th line of English comment, it is marked that the system class loader is obtained, but when we debug, we will find that it is an extension class loader. In practice, we can infer that it should be an extension class loader, because if the class we load already exists under the extension class loader path, then it is wrong for us to directly call the system class loader. The following figure shows the verification of the class loader obtained after debugging.

Summarize

Tomcat breaks the principle of parent delegation, actually breaking the parent delegation in the application class loader, while other class loaders still follow parent delegation.

This is the end of this article about Tomcat class loading mechanism. For more information about Tomcat class loading mechanism, please search previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Tomcat's class loading mechanism process and source code analysis

<<:  Detailed explanation of the practical use of HTML table layout

>>:  Detailed explanation of triangle drawing and clever application examples in CSS

Recommend

How to automatically deploy Linux system using PXE

Table of contents Background Configuring DHCP Edi...

vue front-end HbuliderEslint real-time verification automatic repair settings

Table of contents ESLint plugin installation in H...

HTML page jump passing parameter problem

The effect is as follows: a page After clicking t...

MySQL partition table is classified by month

Table of contents Create a table View the databas...

Windows Server 2008 Tutorial on Monitoring Server Performance

Next, we will learn how to monitor server perform...

jQuery implements accordion effects

This article shares the specific code of jQuery t...

How to set up swap partition SWAP in Linux 7.7

The Swap partition of the Linux system, that is, ...

Detailed steps to install MySQL 5.6 X64 version under Linux

environment: 1. CentOS6.5 X64 2.mysql-5.6.34-linu...

How to change the character set encoding to UTF8 in MySQL 5.5/5.6 under Linux

1. Log in to MySQL and use SHOW VARIABLES LIKE &#...

Solution to the error when importing MySQL big data in Navicat

The data that Navicat has exported cannot be impo...

React non-parent-child component parameter passing example code

React is a JAVASCRIPT library for building user i...

SQL merge operation of query results of tables with different columns

To query two different tables, you need to merge ...