preface

There are several types of loaders in Java

  1. Start class loader: Load core libraries such as rt.jar and charsets. Jar that support the JVM in the JRE lib directory.
  2. The ExtClassLoader is responsible for loading JAR classes from the Ext extension directory in the JRE’s lib directory that support the JVM.
  3. The AppClassLoader is responsible for loading class packages in the ClassPath path, mainly the classes you write yourself.
  4. Custom loader: loads class packages in user-defined paths.

Take a look at each class loader with the following example:

public class ClassLoaderTest { public static void main(String[] args) { System.out.println(Object.class.getClassLoader()); / / Java and DNS service provided by the interaction of the API System. Out. The println (DNSNameService. Class. GetClassLoader ()); System.out.println(ClassLoaderTest.class.getClassLoader()); }}Copy the code

The running results are as follows:

null
sun.misc.Launcher$ExtClassLoader@6d6f6e28
sun.misc.Launcher$AppClassLoader@58644d46

Copy the code

The startup class loader is an instance created under the JVM, so it is null when fetching. The Object class is loaded by the startup class loader, so it is null when fetching. DNSNameService is a class in the Dnsns. jar package of the ext folder in the JAVA_HOME/jre/lib directory. It is loaded by the ExtClassLoader. The ClassLoaderTest class is loaded by AppClassLoader.

The hierarchy of class loaders in Java

As mentioned above, Java class loaders are common classes. ExtClassLoader and AppClassLoader are both subclasses of URLClassLoader, and the INHERITANCE relationship of URL is as follows:

AppClassLoader and ExtClassLoader are subclasses of ClassLoader. As described above, the getLauncher method of sun.misc.Launcher gets an instance of the Launcher when the JVM starts, in which case the Launcher creates an instance of that class through the constructor. Sun.misc.Launcher is constructed as follows:

public Launcher() { Launcher.ExtClassLoader var1; Try {/ / create ExtClassLoader var1 = the Launcher. ExtClassLoader. GetExtClassLoader (); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } the try {/ / create AppClassPoader enclosing loader. = the Launcher AppClassLoader. GetAppClassLoader (var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } // omit the code.... }}Copy the code

By analyzing the sun.misc.Launcher constructor, we know that instance creation of the Sun.misc.Launcher class creates AppClassLoader instances and ExtClassLoader instances. Both classloaders inherit from ClassLoader, and ClassLoader has a parent global variable for ClassLoader, which is also ClassLoader:

public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; // omit the code.... }Copy the code

When sun.misc.Launcher is created, the parent property of ExtClassLoader and AppClassLoader is specified as NULL and ExtClassLoader respectively. The mechanism of a class loader in Java is as follows:

Custom class loaders

This class has two core methods. The first is loadClass(String, Boolean), which implements the parent delegate mechanism, the general logic

  1. First, check to see if the class with the specified name has already been loaded. If it has been loaded, it does not need to be loaded again and returns directly.

  2. If this class has not been loaded, then check if there is a parent loader; If there is a parent loader, it is loaded by the parent (that is, call parent-loadclass (name, false);) . Or call the Bootstrap class loader to load it.

  3. If neither the parent nor the Bootstrap class loader can find the specified class, the findClass method of the current class loader is called to complete the class loading. Another method is findClass. The default implementation is to throw an exception, so our custom classloader mainly overrides the findClass method.

To look at an example, first we write a class that needs to be loaded by a custom classloader, as follows:

package com.dp.jvm; import java.io.PrintStream; public class User { public void say() { System.out.println("hello"); }}Copy the code

Note that the class must be deleted from the project after it is written to avoid loading the AppClassLoader. After compiling, place the class file of this class in the specified directory:

Then write a custom class loader as follows:

class MyClassLoader extends ClassLoader{ private final String path; MyClassLoader(String path) { this.path = path; } /** * override the findClass method of the ClassLoader, @param name @return @throws ClassNotFoundException */ @override protected Class<? > findClass(String name) throws ClassNotFoundException { byte[] byteArrayFromClassName = getByteArrayFromClassName(name); return defineClass(name, byteArrayFromClassName, 0, byteArrayFromClassName.length); } / * * * through the fully qualified class name to get to a class of binary data * @ param name * @ return * / private byte [] getByteArrayFromClassName (String name) {String classPath = convertNameToPath(name); byte[] data = null; int off = 0; int length = 0; try(BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(classPath))) { data = new byte[bufferedInputStream.available()]; while ((length = bufferedInputStream.read(data, off, data.length - off)) > 0) { off += length; } } catch (Exception ex) { ex.printStackTrace(); } return data; } /** * Obtain the bytecode file path of the corresponding class file from the fully qualified name * @param name * @return */ private String convertNameToPath(String name) {String relativePath = name.replace(".", File.separator); String absolutePath = path + File.separator + relativePath + ".class"; return absolutePath; }}Copy the code

Write the test class, load and instantiate the User using a custom class load, and then call its say method as follows:

public class CustomClassLoaderTest {

    public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader("F:\\test");
        Class<?> clazz = myClassLoader.loadClass("com.dp.jvm.User");
        Object o = clazz.newInstance();
        Method say = clazz.getDeclaredMethod("say");
        say.invoke(o);
    }
}

Copy the code

Through the above example, a simple implementation of a custom class loader. I’ll stay and look at the parent delegate mechanism of the class loader.

Parent delegation mechanism

JVM classloaders have a parent-child hierarchy, as shown below:

Note that the parent hierarchy does not refer to inheritance in Java, but rather that each classloading implementation class has a parent global variable of type ClassLoader. One possible problem here is that the global variable named parent is not seen in the custom classloader. This is because the global variable is defined and declared in the ClassLoader.

public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; // omit the code....Copy the code

How do we set the parent property of our custom classloader? How do you know that AppClassLoader is set to AppClassLoader? Because the custom ClassLoader inherits from the ClassLoader, the ClassLoader has a constructor with no arguments as follows:

Protected ClassLoader() {// Call this(checkCreateClassLoader(), getSystemClassLoader()); } private ClassLoader(Void unused, ClassLoader parent) {this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; }}Copy the code

From the point of view of the ClassLoader implementation, the getSystemClassLoader() method gets the system ClassLoader and assigns it to the parent property. GetSystemClassLoader ()

public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm ! = null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; Private static synchronized void initSystemClassLoader() {if (! sclSet) { if (scl ! = null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l ! = null) { Throwable oops = null; scl = l.getClassLoader(); try { scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); } catch (PrivilegedActionException pae) { oops = pae.getCause(); if (oops instanceof InvocationTargetException) { oops = oops.getCause(); } } if (oops ! = null) { if (oops instanceof Error) { throw (Error) oops; } else { // wrap the exception throw new Error(oops); } } } sclSet = true; }}Copy the code

Get the sun.misc.Lanucher instance (singleton) in the getSystemClassLoader method, then call its getClassLoader method to get the system classloader and set it to the parent method. Finally, take a look at sun.misc.lanucher’s getClassLoader method:

public ClassLoader getClassLoader() { return this.loader; } 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); }}Copy the code

Combining sun.misc.lanucher’s getClassLoader and constructor, we know that the system classloader is AppClassLoader. Now that we know the structure of the class loaders in the JVM, let’s look at the structure of the various class loaders in the JVM:

Now that you know the hierarchy of the various classloaders in the JVM, it’s relatively easy to add up and parse the parent delegate mechanism, starting with the flow of parent delegate.

Parent delegate process

The parent delegate process is as follows:

When a class is loaded, it first entrusts the parent loader to find the target class, and then entrusts the upper parent loader to load the target class. If all the parent loaders cannot find the target class in their own classpath, they search for and load the target class in their own classpath. For example, our PrintTest class will first be loaded by the application class loader, and the application class loader will delegate the load to the extension class loader, and the extension class loader will delegate the startup class loader. The top startup class loader searches for the PrintTest class in its own class loading path for a long time and fails to find it. Upon receiving the response, the extended class loader loads the PrintTest class by itself. After searching its own class loading path for a long time, the extended class loader does not find the PrintTest class and returns the request for the PrintTest class to the application class loader. The application classloader looks for the Math class in its own classloading path and loads it when it finds it. The parent delegate mechanism is simply that the father loads it first, and if not, the son loads it.

So why parent delegate?

  • Sandbox security: Self-written java.lang.String.class classes are not loaded, which prevents the core API library from being tampered with.
  • Avoid class reloading: When the parent has already loaded the class, there is no need for the child ClassLoader to load it again to ensure the uniqueness of the loaded class

Parent delegate mechanism source code analysis

The principle of parental delegation is embodied in the loadClass method of ClassLoader:

protected Class<? > loadClass(String name, Boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First check if the Class is loaded Class<? > c = findLoadedClass(name); If (c == null) {long t0 = system.nanotime (); If the parent is also a subclass of ClassLoader //, the method will be entered again to determine whether the parent has a parent ClassLoader, recursion if (parent! = null) { c = parent.loadClass(name, false); } else {// When the class load is not set to parent, then use the boot class loader. C = findBootstrapClassOrNull(name); }} Catch (ClassNotFoundException e) {} if (c == null) { Long t1 = system.nanotime (); c = findClass(name); / /... Omit}} if (resolve) {resolveClass(c); } return c; }}Copy the code

Break parental delegation

Take Tomcat class loading as an example. What if Tomcat uses the default parent delegate class loading mechanism? Let’s consider: Tomcat is a Web container, so what problem does it solve:

  1. A Web container may require two applications to be deployed, and different applications may rely on different versions of the same third-party class library. You cannot require only one copy of the same class library on the same server. Therefore, ensure that the class libraries of each application are independent and isolated from each other. Such as: In the tomcat container, there are two applications A and B, A uses Spring4, and B uses Spring5. If you use parent delegate, you may get A version conflict and error. If the x method does not exist in version 4, but the bytecode of version 4 is loaded first, Then version 5 will not be loading (the class has the same qualified name), and calling method x in program B will throw a method that does not have an exception.
  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, 10 copies of the same class library are loaded into the virtual machine. Servlet dependencies are common in Web applications. In Maven, dependencies are provided, and web applications are serlverts of the container they use.
  3. The Web container also has its own dependent class library, not to be confused with the application’s class library. For security reasons, the container’s libraries should be separated from the program’s libraries.
  4. The Web container needs to support JSP modification. As we know, JSP files eventually need to be compiled into class files before they can be run in the virtual machine, but it is common to modify JSPS after a program runs, and the Web container needs to support JSP modification without restarting. Anyone who knows JSP mechanism knows that JSP is resolved into a corresponding Servlet(often said that a JSP is a Servlet), JSP is dynamically generated by the. Class file to achieve dynamic resources.

Let’s look at our question: Does it work if Tomcat uses the default parent delegate class loading mechanism?

  • The first problem is that you can’t load two different versions of the same library if you use the default class loader mechanism. The default class loader only cares about your fully qualified class name and only has one copy.
  • Second, the default class loader can be implemented because its job is to ensure uniqueness.
  • The third question is the same as the first.
  • Let’s look at the fourth problem, we want to how we implement hot loading JSP file, JSP file is actually the class file, so if we change, but the class name is the same, the class loader will directly fetch the method area already exists, the modified JSP is not reloaded. So what to do? We can unmount the class loader for this JSP file directly, so you should be aware that each JSP file has a unique class loader. When a JSP file is modified, the JSP class loader is unmounted directly. Re-create the class loader and reload the JSP file.

The last

Thank you for reading here, after reading what do not understand, you can ask me in the comments section, if you think the article is helpful to you, remember to give me a thumbs up, every day we will share Java related technical articles or industry information, welcome to pay attention to and forward the article!