The overall flow of JVM class loading

The overall class loading process is shown in the figure below:

For example, I want to load a Demo class:

  1. The JVM will be started first (the underlying JVM is implemented by C++, under Windows to start the JVM is called jvm.dll java.exe method to create the JVM).
  2. Create a boot class loader from the JVM (again with C++ implementation underneath)
  3. The boot classloader creates an instance of the JVM startup class Sun.misc.Launcher class.
  4. By calling the sun. The misc. The Launcher. GetLauncher () method, this method will return to the Launcher class internal static variables: private static the Launcher the Launcher = new Launcher (). During the new Launcher() process, you create a classloader that runs the class’s own class. The AppClassLoader is an application class loader.
  5. The class loader is obtained using the launcher.getClassloader () method.
  6. The Demo class instance is loaded by calling classLoader.getClass(” com.test.demo “).
  7. When the class is successfully loaded, the JVM invokes the main method entry of the executing class to execute the program logic
  8. After execution, destroy the JVM.

Class loading process

Classloader.loadclass (” com.test.demo “) This is the key part, I believe that those of you who have known the parental delegation model, is here. First, let’s take a look at the loading process of a class:

The loading process of a class is divided into five steps: loading, verification, preparation, parsing, and initialization

  1. Load: Loads class information from disk into memory
  2. Validation: Verifies the validity of Java bytecode
  3. Preparation: Assigns an initial default value to a static variable (the default value is defined by Java. Note that static variables are static and final is a constant.)
  4. Parsing: The process of parsing is static linking, that is, converting symbolic references to direct references. This stage will transform some static methods such as main() method (symbolic reference) to refer to the memory address or handle of the data (direct reference). Some methods will not be parsed at this stage, but will perform the transformation at run time, which we call dynamic linking.

Class loader

This article focuses on the class loader, the above class loading process is done through the class loader. Java class loaders are classified as follows:

  1. Jar files in the jre lib directory (rt.jar,chartsets.jar)
  2. The extension class loader is mainly responsible for loading JAR packages in the JRE lib/ext directory
  3. The application class loader is responsible for loading the class packages (.class files) in the classPath.
  4. Custom class loader: loads a class package in a custom path

The class loader is initialized in the constructor of the Launcher class. Here’s a sample of the source code:

public class Launcher {
 	private static URLStreamHandlerFactory factory = new Factory();
    private static Launcher launcher = new Launcher();
    private static String bootClassPath =
        System.getProperty("sun.boot.class.path");

    public static Launcher getLauncher(a) {
        return launcher;
    }

    private ClassLoader loader;
 	public Launcher(a) {
        // Create the extension class loader
        ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader", e);
        }

        // Now create the class loader to use to launch the application
        try {
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader", e);
        }

        // Also set the context class loader for the primordial thread.
        Thread.currentThread().setContextClassLoader(loader);

        // Finally, install a security manager if requested
        String s = System.getProperty("java.security.manager");
        if(s ! =null) {
            // init FileSystem machinery before SecurityManager installation
            sun.nio.fs.DefaultFileSystemProvider.create();

            SecurityManager sm = null;
            if ("".equals(s) || "default".equals(s)) {
                sm = new java.lang.SecurityManager();
            } else {
                try {
                    sm = (SecurityManager)loader.loadClass(s).newInstance();
                } catch (IllegalAccessException e) {
                } catch (InstantiationException e) {
                } catch (ClassNotFoundException e) {
                } catch (ClassCastException e) {
                }
            }
            if(sm ! =null) {
                System.setSecurityManager(sm);
            } else {
                throw new InternalError(
                    "Could not create SecurityManager: "+ s); }}}/* * Returns the class loader used to launch the main application. */
    public ClassLoader getClassLoader(a) {
        returnloader; }}Copy the code

Focus on the following lines of code:

internalstaticExamples of the launcher. The singletonprivate static Launcher launcher = newLauncher(); Class loaderprivateClassLoader loader; To obtainluancher
 
 public static Launcher getLauncher(a) {
        returnlauncher; } get the class loaderpublic ClassLoader getClassLoader(a) {
        returnloader; } create extension ClassLoader extCL; extcl = ExtClassLoader.getExtClassLoader(); Here you can see that the loader is finally assigned to AppClassLoader, which is the application class loader. We can see from the loading process above that all classes are called, Loader. The loadClass () method to load the class So we write the program (not specify special loading way) through the application class loader began to load the loader. = AppClassLoader getAppClassLoader (extcl);Copy the code

Parental delegation model

The class loader loading process is shown in the following figure:

The general class loading process is first through the AppClassLoader application class loader (this can be seen from the above source code), and this class loading process has a parent delegation mechanism, that is, the application class loader will first determine whether the class has been loaded, if the class has been loaded before, directly return the class information. If not, delegate to the parent class loader.Pay attention toThe parent class loader does the same thing, delegating the load to the boot class loader, and the boot class loader returns the load directly. If not, it is returned to the subclass loader to try to load it. Repeat this process until the load succeeds or fails.

Now let’s take a look at what’s going on underneath Java at the source level. The following figure shows the inheritance relationship of AppClassLoaderAppClassLoader. LoadClass () method:


        /** * Override loadClass so we can checkPackageAccess. */
        publicClass<? > loadClass(String name,boolean resolve)
            throws ClassNotFoundException
        {
            int i = name.lastIndexOf('. ');
            if(i ! = -1) {
                SecurityManager sm = System.getSecurityManager();
                if(sm ! =null) {
                    sm.checkPackageAccess(name.substring(0, i)); }}if (ucp.knownToNotExist(name)) {
                // The class of the given name is not found in the parent
                // class loader as well as its local URLClassPath.
                // Check if this class has already been defined dynamically;
                // if so, return the loaded class; otherwise, skip the parent
                // delegation and findClass.Class<? > c = findLoadedClass(name);if(c ! =null) {
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
                throw new ClassNotFoundException(name);
            }

            return (super.loadClass(name, resolve));
        }
Copy the code

The application class loader, after a series of verification judgments, will go to the last line of code to call the parent class loader’s class loading method:

super.loadClass(name,resolve);
Copy the code

AppClassLoader’s parent class inherits the abstract ClassLoader. This eventually calls the loadClass() method of the ClassLoader implementation. The source code is as follows:

 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

It first checks to see if the class has been loaded, and returns if it has

   // First, check if the class has already been loadedClass<? > c = findLoadedClass(name);Copy the code

FindBootstrapClassOrNull is a method implemented locally in C++. If it is not found, the parent class loader is called null. If it is not found, the parent class loader is called null. Will use the boot class loader to try to load

if(parent ! =null) {
        c = parent.loadClass(name, false);
} else {
        c = findBootstrapClassOrNull(name);
}
Copy the code

Here the whole thing is strung together, and ExtClassLoader will also eventually call the loadClass method implemented by ClassLoaderThis process is the delegate up process. If the parent delegate is not loaded, we will call our own findClass() method and try to load it ourselves:

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();
}
Copy the code

This is the entire flow of the classloader’s parent delegate.

How do I customize class loaders

Both AppClassLoader and ExtClassLoader inherit the ClassLoader root class. So if we want to customize our own loader, we simply implement a class that inherits the ClassLoader class and overrides its findClass method. Talk is Cheap Show me the Code

package think_in_classloader;

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

public class MyTestClassLoader extends ClassLoader{

        private String classPath;

        private MyTestClassLoader(String classPath){
            System.out.println("Initialize:"+classPath);
            this.classPath=classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name=name.replaceAll("\ \."."/");
            FileInputStream fileInputStream=new FileInputStream(classPath+"/"+name+".class");
            int len=fileInputStream.available();
            byte[] data=new byte[len];
            fileInputStream.read(data);
            fileInputStream.close();
            return data;
        }

        @Override
        protectedClass<? > findClass(String name)throws ClassNotFoundException {
            try {
                byte[] data=loadByte(name);
                return defineClass(name,data,0,data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw newClassNotFoundException(); }}public static void main(String[] args) throws Exception {
            MyTestClassLoader myTestClassLoader=new MyTestClassLoader("D:\\test");
            Class clazz=myTestClassLoader.loadClass("test.Demo");
            Object object=clazz.newInstance();
            Method method=clazz.getDeclaredMethod("sayHello",String.class);
            method.invoke(object,"Word j"); System.out.println(clazz.getClassLoader().getClass().getName()); }}Copy the code

Why design the parental delegation model?

With all the underlying principles, I think you’re all curious. Why design such a parent delegate mechanism? I think it can be understood from two aspects:

  1. Sandbox security: Prevents Java core libraries from being modified (java.long.string. class written by yourself will not be loaded)
  2. Avoid class reloading: When the parent class loader has loaded the class, the subclass loader does not need to load again, ensuring the uniqueness of class loading.

How to break the parental delegation model

Before we talk about how to break the parental delegation model, you might wonder why we break the parental delegation model? For example, in the case of Tomcat, we all know that tomcat can deploy multiple war packages, so if we deploy multiple War packages that depend on different versions of the class library, For example, if one depends on Spring 4 and one depends on Spring 5, then depending on the parent delegate mechanism, Spring 4 is loaded first, so another war package that relies on Spring 5 will not load Spring 5 when it loads because it will return the loaded Spring 4 directly. At this point, there is the problem of inconsistent versions. So Tomcat needs to implement its own class loader to break the parental delegation model and generate its own class loader for each WAR package

So how do you break the parental delegation model? It’s very simple. We just need to override the loadClass method.

package think_in_classloader;

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

public class MyTestClassLoader extends ClassLoader{

        private  int num=9;

        private String classPath;

        private MyTestClassLoader(String classPath){
            System.out.println("Initialize:"+classPath);
            this.classPath=classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name=name.replaceAll("\ \."."/");
            FileInputStream fileInputStream=new FileInputStream(classPath+"/"+name+".class");
            int len=fileInputStream.available();
            byte[] data=new byte[len];
            fileInputStream.read(data);
            fileInputStream.close();
            return data;
        }

        @Override
        protectedClass<? > findClass(String name)throws ClassNotFoundException {
            try {
                byte[] data=loadByte(name);
                return defineClass(name,data,0,data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw newClassNotFoundException(); }}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();



                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    System.out.println(name);
                    if (name.startsWith("test")){
                        c = findClass(name);
                    }else {
                        c=getParent().loadClass(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);
            }
            returnc; }}public static void main(String[] args) throws Exception {
            MyTestClassLoader myTestClassLoader=new MyTestClassLoader("D:\\test");
            Class clazz=myTestClassLoader.loadClass("test.Demo");
            Object object=clazz.newInstance();
            Method method=clazz.getDeclaredMethod("sayHello",String.class);
            method.invoke(object,"Word j"); System.out.println(clazz.getClassLoader().getClass().getName()); }}Copy the code