What is a class loader?

As you can see, this simple process is the entire process of running Java code. The JVM first compiles the Java source file into a.class bytecode file, and then loads the class file into memory with the class loader for our use. You can see that ClassLoader plays a very important role in this.

2. What class loaders are available in Java?

First of all, we need to know that there are three default loaders that come with the JVM baseStart the Bootstrap ClassLoader,Extension ClassLoader Extension ClassLoader,The Application ClassLoader. The relationship between them is as follows:

Let’s take a look at the loading paths of the three classloaders:

2.1 Loading path of BootstrapClassLoader

		System.out.println("BootstrapClassLoader loading path:");
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL url : urls) {
            System.out.println(url.getPath());
        }
Copy the code
BootstrapClassLoader loading path: /F:/installSoftware/JDK8/jre/lib/resources.jar /F:/installSoftware/JDK8/jre/lib/rt.jar /F:/installSoftware/JDK8/jre/lib/sunrsasign.jar /F:/installSoftware/JDK8/jre/lib/jsse.jar /F:/installSoftware/JDK8/jre/lib/jce.jar /F:/installSoftware/JDK8/jre/lib/charsets.jar /F:/installSoftware/JDK8/jre/lib/jfr.jar /F:/installSoftware/JDK8/jre/classesCopy the code

The BootstrapClassLoader is loaded in the JDK installation directory: %JAVA_HOME%jre/lib

2.2 Loading Path of EXTClassLoader

		URLClassLoader extClassloader = (URLClassLoader) ClassLoader.getSystemClassLoader().getParent();
        System.out.println("EXTClassLoader loading path:");
        URL[] urls = extClassloader.getURLs();
        for (URL url : urls) {
            System.out.println(url.getPath());
        }
Copy the code
EXTClassLoader load path:  /F:/installSoftware/JDK8/jre/lib/ext/access-bridge-64.jar /F:/installSoftware/JDK8/jre/lib/ext/ClassLoaderTest.class /F:/installSoftware/JDK8/jre/lib/ext/cldrdata.jar /F:/installSoftware/JDK8/jre/lib/ext/dnsns.jar /F:/installSoftware/JDK8/jre/lib/ext/jaccess.jar /F:/installSoftware/JDK8/jre/lib/ext/jfxrt.jar /F:/installSoftware/JDK8/jre/lib/ext/localedata.jar /F:/installSoftware/JDK8/jre/lib/ext/nashorn.jar /F:/installSoftware/JDK8/jre/lib/ext/sunec.jar /F:/installSoftware/JDK8/jre/lib/ext/sunjce_provider.jar /F:/installSoftware/JDK8/jre/lib/ext/sunmscapi.jar /F:/installSoftware/JDK8/jre/lib/ext/sunpkcs11.jar /F:/installSoftware/JDK8/jre/lib/ext/zipfs.jarCopy the code

The EXTClassLoader is loaded in the JDK installation directory: %JAVA_HOME%jre/lib/ext

2.3 AppClassLoader Loading Path

The project directory structure is as follows:

		URLClassLoader appClassloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        System.out.println("AppClassLoader loading path:");
        urls = appClassloader.getURLs();
        for (URL url : urls) {
            System.out.println(url.getPath());
        }
Copy the code

You can see that the AppClassLoader mainly loads three paths:

  • External Libraries Specifies the path of the dependent External JAR package
  • The project path out/production/algorithm (target/classes/ if it is a Maven project) after the project source is compiled, and the folder marked as Resources under the project path will also be loaded.
  • Idea_rt. jar in the idea installation directory

3. Class loading process

Jar contains java.lang.*, java.util.*, etc. EXTClassLoader mainly loads /lib/ext/ extension classes; The AppClassLoader mainly loads the following class libraries for our user application. So let’s start with our closest classloader, AppClassLoader, and take a look at the loading process: AppClassLoader (EXTClassLoader) and EXTClassLoader (EXTClassLoader) are not the same as EXTClassLoader and AppClassLoader.

What is the parent of AppClassLoader?

To start, we need to start with the Launcher class, the entry class for our Java application.

public class Launcher {
    private static Launcher launcher = new Launcher();
    private ClassLoader loader;
    public Launcher(a) {
        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);
        }

        Thread.currentThread().setContextClassLoader(this.loader); }}Copy the code

(The above code has been deleted, only the key parts remain)

  • Line 2 initializes a Launcher object.
  • Line 3 declares a variable loader of type ClassLoader
  • Line 5 declares a variable var1 of type ExtClassLoader
  • Line 7 assigns an instance of ExtClassLoader(getExtClassLoader() gets a singleton) to var1
  • Here we go!! Line 13 the Launcher. AppClassLoader. GetAppClassLoader () method, the parameters are ExtClassLoader object, good, Let’s now go to the getAppClassLoader method (focus on the direction of the ExtClassLoader object below).
		public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            final String var1 = System.getProperty("java.class.path");
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run(a) {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    return newLauncher.AppClassLoader(var1x, var0); }}); }Copy the code
  • The first line System. GetProperty (” Java.class. Path “); Represents the entire path of the AppClassLoader we printed above
  • Return new Launcher.AppClassLoader(var1x, var0); This line of code creates an AppClassLoader object with the argument var0 from our Launcher constructor call to getAppClassLoader (var0 is an ExtClassLoader object).
		AppClassLoader(URL[] var1, ClassLoader var2) {
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }
Copy the code

As you can see, var2 (var2 is the ExtClassLoader object) is passed into the parent class constructor. I already know that URLClassLoader is the parent class of AppClassLoader. So we are now in the constructor of AppClassLoader’s parent class, URLClassLoader.

public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
        super(parent);
    }
Copy the code

In this case, we know that parent is the ExtClassLoader object passed in above, and the first line passes parent to the constructor of the URLClassLoader’s parent class, SecureClassLoader.

protected SecureClassLoader(ClassLoader parent) {
        super(parent);
    }
Copy the code

Again, let’s go to the constructor of SecureClassLoader’s parent class ClassLoader.

	private final ClassLoader parent;
	// Passes the parent argument to this constructor, and then to the private constructor below
 	protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
    }
    
Copy the code

You can see that when the Java application initialstarts (when a Launcher object is new) the ExtClassLoader ClassLoader object is sent all the way from the AppClassLoader constructor to the ClassLoader instance and assigned to the parent variable. With that said, just keep in mind that the parent of AppClassLoader is ExtClassLoader.

And then we can do the same thing to see what the parent of ExtClassLoader is.

  • First go to the ExtClassLoader constructor.
		public ExtClassLoader(File[] var1) throws IOException {
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }
Copy the code

You can see that the ClassLoader argument passed in calling the parent constructor is empty. Therefore, we can still conclude that the parent of ExtClassLoader is null.

With these two important pieces of information in mind, learning about class loading in Java is now much easier!!


3.2 Specific process of ClassLoader loading

Let’s start from AppClassLoader class, in this class, we can see loadClass this method, the method overloading this class loadClass method, execution of simple look at the below method.

publicClass<? > loadClass(String var1,boolean var2) throws ClassNotFoundException {
            int var3 = var1.lastIndexOf(46);
            if(var3 ! = -1) {
                SecurityManager var4 = System.getSecurityManager();
                if(var4 ! =null) {
                    var4.checkPackageAccess(var1.substring(0, var3)); }}if (this.ucp.knownToNotExist(var1)) {
            // Check the cache to see if var1 has been loaded, and return the instance var5 if it has been loaded
                Class var5 = this.findLoadedClass(var1);
                if(var5 ! =null) {
                    if (var2) {
                        this.resolveClass(var5);
                    }
                    return var5;
                } else {
                    throw newClassNotFoundException(var1); }}else {
            // The class is not loaded. Pass the argument to the parent method and call loadClass
                return super.loadClass(var1, var2); }}Copy the code

The loadClass method of the parent class is called if the class has not been loaded.

Now let’s go to the loadClass in the parent ClassLoader class and see how it executes. .

protectedClass<? > loadClass(String name,boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loadedClass<? > 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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); }}if (resolve) {
                resolveClass(c);
            }
            returnc; }}Copy the code
  • As with the first step in AppClassLoader, findLoadedClass is called to determine whether the class file has already been loaded in memory.
  • We then call the parent loadClass method to load it. Remember what parent was? The parent object is the loadClass method of the ExtClassLoader, but the ExtClassLoader does not overload the loadClass method, so it recursively calls the current loadClass method.
  • Parent = null; parent = null; parent = null; So the findBootstrapClassOrNull method is called to find if it has been loaded, and the following native method is called.
private nativeClass<? > findBootstrapClass(String name);Copy the code

The parent in ExtClassLoader is null because the BootstrapClassLoader is a class loader written in C++ and we cannot get a reference to it in Java code.

  • If c==null is true, the findClass method is called. Note that the BootstrapClassLoader is now looking for the path from which the BootstrapClassLoader loadedsun.mic.boot.class.
  • If it is not found, null is returned to the position of the recursion at the previous level, i.ec = parent.loadClass(name, false);If c==null is true, the findClass method is called and the ExtClassLoader load path is now foundjava.ext.dirs.
  • If it is not found, null is returned to the position of the recursion at the previous level, i.ec = parent.loadClass(name, false);If c==null is true, the findClass method is called and the AppClassLoader load path is now foundjava.class.path.
  • If none of the above is found, an exception ClassNotFoundException is reported.

To summarize the whole process of class loading: (1) Check the cache to see if the class file has been loaded, if not, delegate to the parent class loader. (2) Recursion, repeat the first step. (3) If the ExtClassLoader has not been loaded, use BootstrapClassLoader to check whether the class file has been loaded in the cache. If the search is empty, the search is performed in the sun.mic.boot.class path and returns on success, otherwise the child loader is called to search. (4) If the BootstrapClassLoader fails to find, use ExtClassLoader to search in java.ext.dirs. If the search succeeds, return; otherwise, call child loader to search. (5) If the ExtClassLoader fails to find the java.class.path file, use AppClassLoader to find the java.class.path file, return the search success, otherwise call subclass search. (6) If none is found, an exception will be thrown. This process is also known as the parent delegate load process, delegate from the bottom up, lookup from the top down.

4. Break the parental entrustment mechanism?

When I was learning Tomcat, I saw a bunch of class loaders defined by Tomcat itself and thought that class loading was carried out in accordance with the parent delegation mechanism. However, I checked the information online and found that this was not the case. At the same time, combined with the experience of deploying Tomcat Web projects, I found that it would definitely not work to use the traditional parent delegation mechanism for class loading. Let’s analyze the specific reasons.

4.1 Class loader for Tomcat

Tomcat has five classloaders. In the initial class loader of Tomcat, I found this source code (Tomcat version 8.5) :

private void initClassLoaders(a) {
        try {
            commonLoader = createClassLoader("common".null);
            if (commonLoader == null) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader = this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1); }}Copy the code

Note that the parent loader is returned if the configuration read is empty. .

private ClassLoader createClassLoader(String name, ClassLoader parent)
            throws Exception {

        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            returnparent; value = replace(value); . }Copy the code

The above code shows the commonLoader, catalinaLoader, sharedLoader three class loader creation. The second parameter to createClassLoader is the parent of the current class loader. CommonLoader is the parent of catalinaLoader and sharedLoader. The parent of commonLoader is null. The default value is AppClassLoader. The three loading paths are configured in Catalina. properties and correspond to the configurations of common.loader, server.loader, and shared.loader respectively.

common.loader="${catalina.base}/lib"."${catalina.base}/lib/*.jar"."${catalina.home}/lib"."${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
Copy the code

CatalinaLoader, sharedLoader, parent loader, sharedLoader, parent loader, sharedLoader, parent loaderLoader, sharedLoader, shareinaloader, and Shareinaloader are commonLoaders. The default parent loader for all three is AppClassLoader!!. So by default the parent relationship of the three classloaders should look like this!!The loading path is also found in the start method of the WebappClassLoader, as follows.

public void start(a) throws LifecycleException {

        state = LifecycleState.STARTING_PREP;

        WebResource[] classesResources = resources.getResources("/WEB-INF/classes");
        for (WebResource classes : classesResources) {
            if (classes.isDirectory() && classes.canRead()) {
                localRepositories.add(classes.getURL());
            }
        }
        WebResource[] jars = resources.listResources("/WEB-INF/lib");
        for (WebResource jar : jars) {
            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
                localRepositories.add(jar.getURL());
                jarModificationTimes.put(
                        jar.getName(), Long.valueOf(jar.getLastModified()));
            }
        }

        state = LifecycleState.STARTED;
    }
Copy the code

Obviously, WebappClassLoader is mainly used to load Web applications/WEB-INF/classesand/WEB-INF/libFiles in both paths.

4.2 The loading range of each Class loader of Tomcat

CommonLoader: Basic Tomcat class loader. Classes in the loading path can be accessed by the Tomcat container itself and various WebApps. CatalinaLoader: Private class loader for the Tomcat container. Classes in the loading path are not visible to Webapp. SharedLoader: a class loader shared by webApps. The classes in the load path are visible to all WebApps but not to the Tomcat container. WebappClassLoader: A private class loader for each Webapp. The classes in the loading path are visible only to the current Webapp.

4.2 How does Tomcat break the parental entrustment mechanism?

Our experience with Tomcat is that if you have 10 Web applications that all use Spring, you can place spring jars in a common or shared directory for those applications to share. The purpose of Spring is to manage the beans of each Web application. The getBean should have access to the application classes, and the user’s program is obviously placed in the /WebApp/WEB-INF directory (loaded by the WebAppClassLoader). How does a Spring container in CommonClassLoader or SharedClassLoader load classes in user programs (/WebApp/WEB-INF/) that are not in its loading scope?

The answer: Spring doesn’t care where it’s placed, it loads classes using the current thread context loader, which is set to WebAppClassLoader by default, which WebApp calls Spring, Spring gets the application’s own WebAppClassLoader to load the bean, which is awesome! Setting the ContextClassLoader is more complex than this.

Thread.currentThread().setContextClassLoader(WebAppClassLoader);
Thread.currentThread().getContextClassLoader();
Copy the code

The Tomcat ClassLoader violates the parent delegate model. The Tomcat ClassLoader violates the parent delegate model. The Tomcat ClassLoader violates the parent delegate model