Related articles Java Virtual Machine series

preface

Hot repair and plug-in is currently more popular technology, in order to better master them need to understand ClassLoader, so there is the generation of this series, this article we first learn Java ClassLoader.

1. This type

In this article, I mentioned the Class loading subsystem, which uses a variety of classloaders to find and load Class files into the Java virtual machine (JVM). There are two main types of classloaders in Java, system classloaders and custom classloaders. There are three system classloaders, Bootstrap ClassLoader, Extensions ClassLoader, and App ClassLoader.

1.1 the Bootstrap this

C/C++ code to load the system classes required by the Java virtual machine, such as java.lang.*, java.uti.*, they default in $JAVA_HOME/jre/lib directory, You can also change the loading directory of the Bootstrap ClassLoader by specifying the -xBootCLASspath option when starting the Java VIRTUAL machine. The Java virtual machine is started by creating an initial class by Bootstrap ClassLoader. Since Bootstrap ClassLoader is implemented in C/C++, it cannot be accessed by Java code. Note that Bootstrap ClassLoader does not inherit from java.lang.classLoader. We can use the following code to calculate the directory loaded by Bootstrap ClassLoader:

public class ClassLoaderTest {
    public static void main(String[]args) {
        System.out.println(System.getProperty("sun.boot.class.path")); }}Copy the code

The printed result is:

C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ lib \ resources jar; C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ lib \ rt jar; C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ lib \ sunrsasign jar; C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ lib \ jsse jar; C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ lib \ jce jar; C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ lib \ charsets jar; C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ lib \ JFR jar; C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ classesCopy the code

You can find almost all jars in $JAVA_HOME/jre/lib, including rt.jar, resources.jar, and charsets.jar.

1.2 Extensions of this

The jar package of the extension class is usually placed in the $JAVA_HOME/jre/lib/ext directory to provide additional functionality in addition to the system class. You can also add and modify the Extensions ClassLoader loading path using the -djava.ext. dirs option. To get the Extensions ClassLoader load directory, use the following code:

System.out.println(System.getProperty("java.ext.dirs"));Copy the code

The printed result is:

C: \ Program Files \ Java \ jdk1.8.0 _102 \ jre \ lib \ ext. C:\Windows\Sun\Java\lib\extCopy the code

1.3 this App

Responsible for loading all jar and Class files in the current application’s Classpath directory. You can also load JAR and class files in the directory specified by the -djava.class. path option.

1.4 the Custom this

In addition to the system-provided class loaders, you can also customize class loaders. Custom class loaders implement their own class loaders by inheriting the java.lang.ClassLoader class. In addition to Bootstrap ClassLoader, The Extensions ClassLoader and App ClassLoader also inherit the java.lang.ClassLoader class. More on custom class loaders later.

2. Inheritance relationship of ClassLoader

How many types of class loaders are needed to run a Java program? As shown below.

public class ClassLoaderTest {
    public static void main(String[] args) {
        ClassLoader loader = ClassLoaderTest.class.getClassLoader();
        while(loader ! =null) {
            System.out.println(loader);/ / 1loader = loader.getParent(); }}}Copy the code

First we get the classloader for the current class ClassLoaderTest and print it out at comment 1. Then we print out the parent of the classloader for the current class until no parent terminates the loop. The output is as follows:

sun.misc.Launcher$AppClassLoader@75b84c92
sun.misc.Launcher$ExtClassLoader@1b6d3586Copy the code

Line 1 says that the classloader that loads ClassLoaderTest is AppClassLoader, and line 2 says that the parent of AppClassLoader is ExtClassLoader. Bootstrap ClassLoader, the parent of ExtClassLoader, is not printed because Bootstrap ClassLoader is written in C/C++ and is not a Java class, so we cannot get a reference to it in Java code.

We know that the system provides three types of classloaders, but the system provides more than three classloader-related classes. In addition, the parent ClassLoader of AppClassLoader is ExtClassLoader, which does not mean that AppClassLoader inherits from ExtClassLoader. The inheritance relationship of ClassLoader is as follows.

As you can see, there are five classLoader-related classes in the figure above. Here are a few of them:

  • ClassLoader is an abstract class that defines the main functions of a ClassLoader.
  • SecureClassLoader inherits the abstract class ClassLoader, but SecureClassLoader is not the implementation of ClassLoader. SecureClassLoader extends the ClassLoader class and adds permissions to enhance ClassLoader security.
  • URLClassLoader inherits from SecureClassLoader and is used to load classes and resources from JAR files and folders through URl paths.
  • ExtClassLoader and AppClassLoader both inherit from URLClassLoader. They are both internal classes of Launcher, which is an entry application to the Java VIRTUAL machine. Both ExtClassLoader and AppClassLoader are initialized in the Launcher.

3. Parental delegation mode

3.1 Characteristics of the parent entrustment model

The Class loader uses the parent delegation mode to find the Class. In the so-called parent delegation mode, it first determines whether the Class has been loaded. If not, it does not find the Class itself, but delegates it to the parent loader to find the Class. If the Bootstrap ClassLoader finds the Class, the Bootstrap ClassLoader returns the Class directly. If the Bootstrap ClassLoader does not find the Class, the Bootstrap ClassLoader returns the Class directly. If the Bootstrap ClassLoader does not find the Class, the Bootstrap ClassLoader returns the Class directly. This might be a little abstract, but let’s look at the picture below.

We know that the Class loading subsystem is used to find and load Class files into the Java virtual machine. Suppose we want to load a Class file on disk D, and the Class loader provided by the system cannot meet the requirements, so we need to customize the Class loader inherited from java.lang.ClassLoader. And overwrites its findClass method. The procedure for loading the Class file of disk D is as follows:

  1. The custom Class loader first checks the cache to see if the Class file has been loaded. If it has been loaded, it returns the Class. If it has not been loaded, it delegates to the parent loader, the App ClassLoader.
  2. Recurse step 1 in the direction of the red dotted line in the figure above.
  3. Delegate to the Bootstrap ClassLoader. If Bootstrap ClassLoader does not find the Class file in the cache, If the Class is found, the Class is returned. If it is not found, it is handed to the Extensions ClassLoader.
  4. The Extensions ClassLoader searches for jar packages in $JAVA_HOME/jre/lib/ext or -djava.ext. dirs and returns them to App ClassLoader if they are not found.
  5. App ClassLoade looks for jar packages and Class files in the Classpath directory or in the directory specified by -djava.ext. dirs, returns if found, fails to find our custom Class loader, and throws an exception if not found.

In general, after the Class file is loaded into the Class loading subsystem, the delegate is carried out from top to bottom along the direction of the red dotted line in the figure, and then the search is carried out from top to bottom along the direction of the black dotted line. The whole process is first up and then down.

The class loading steps are also reflected in the JDK8 source code to look at the ClassLoader method of the abstract class, as shown below.

 protectedClass<? > More ... loadClass(String name,boolean resolve)
         throws ClassNotFoundException
     {
         synchronized(getClassLoadingLock(name)) { Class<? > c = findLoadedClass(name);/ / 1
             if (c == null) {
                 long t0 = System.nanoTime();
                 try {
                     if(parent ! =null) {
                         c = parent.loadClass(name, false);/ / 2
                     } else {
                         c = findBootstrapClassOrNull(name);/ / 3}}catch (ClassNotFoundException e) {            
                 }
                 if (c == null) {
                     // If still not found, then invoke findClass in order
                     // to find the class.
                     long t1 = System.nanoTime();
                     c = findClass(name);/ / 4
                     // 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

Comment 1 checks to see if the class is loaded. If it is, the following code does not execute and returns the class. If there is no load, execution continues down. In comment 2, if the parent class loader is not null, the loadClass method of the parent class loader is called. If the parent class loader is null, the findBootstrapClassOrNull method in comment 3 is called. This method internally calls the Native method findLoadedClass0, The findLoadedClass0 method will eventually use Bootstrap Classloader to find the class. If the Bootstrap Classloader does not find the class, the delegate does not find the class, and calls the findClass method in comment 4 to continue the search.

3.2 Benefits of the parent delegation model

There are two main advantages of adopting the parental delegation model:

  1. Avoid double loading. If the Class has already been loaded once, it does not need to be loaded again and is read directly from the cache first.
  2. If you do not use the parent delegate mode, you can customize a String class to replace the system’s String class. This is obviously a security risk. If you use the parent delegate mode, the system’s String class will be loaded when the Java virtual machine starts. There is no way to customize the String class to replace the system’s String class, unless we change the default algorithm for the classloader to search for classes. Also, the Java virtual machine considers two classes to be the same class only if they have the same class name and are loaded by the same classloader, which is obviously not easy to fool.

4. Customize the ClassLoader

The Class loader provided by the system can load only jar packages and Class files in the specified directory. If you want to load JAR packages and Class files in a file on the network or disk D, you need to customize the ClassLoader. Implementing a custom ClassLoader requires two steps:

  1. Define a custom ClassLoade that inherits the abstract ClassLoader.
  2. Overwrite the findClass method and call the defineClass method in the findClass method.

Let’s customize a ClassLoader to load the Class file in D:\lib.

4.1 Writing test Class files

Start by writing the test Class and generating a Class file, as shown below.

package com.example;
public class Jobs {
    public void say(a) {
        System.out.println("One more thing"); }}Copy the code

Put the jobs. Java in D:\lib, use CMD to go to the D:\lib directory, execute Javac Jobs. Java to compile the Java file, then generate jobs.class in D:\lib.

4.2 Writing a Custom ClassLoader

Next, write a custom ClassLoader, as shown below.

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class DiskClassLoader extends ClassLoader {
    private String path;
    public DiskClassLoader(String path) {
        this.path = path;
    }
    @Override
    protectedClass<? > findClass(String name)throws ClassNotFoundException {
        Class clazz = null;
        byte[] classData = loadClassData(name);/ / 1
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            clazz= defineClass(name, classData, 0, classData.length);/ / 2
        }
        return clazz;
    }
    private byte[] loadClassData(String name) {
        String fileName = getFileName(name);
        File file = new File(path,fileName);
        InputStream in=null;
        ByteArrayOutputStream out=null;
        try {
             in = new FileInputStream(file);
             out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length=0;
            while((length = in.read(buffer)) ! = -1) {
                out.write(buffer, 0, length);
            }
            return out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(in! =null) { in.close(); }}catch (IOException e) {
                e.printStackTrace();
            }
            try{
                if(out! =null) { out.close(); }}catch(IOException e){ e.printStackTrace(); }}return null;
    }
    private String getFileName(String name) {
        int index = name.lastIndexOf('. ');
        if(index == -1) {// If no '.' is found, add the. Class at the end
            return name+".class";
        }else{
            return name.substring(index+1) +".class"; }}}Copy the code

A few things to note about this code are that the loadClassData method at comment 1 gets the bytecode array of the class file and calls the defineClass method at comment 2 to convert the bytecode array of the class file into an instance of the class class. The loadClassData method needs to operate on streams, close streams ina finally block, and try in and out separately. If in and out are both ina try statement, then if in.close() fails, Out.close () cannot be executed.

Finally, we verify that the DiskClassLoader is available with the code shown below.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassLoaderTest {
    public static void main(String[] args) {
        DiskClassLoader diskClassLoader = new DiskClassLoader("D:\\lib");/ / 1
        try {
            Class c = diskClassLoader.loadClass("com.example.Jobs");/ / 2
            if(c ! =null) {
                try {
                    Object obj = c.newInstance();
                    System.out.println(obj.getClass().getClassLoader());
                    Method method = c.getDeclaredMethod("say".null);
                    method.invoke(obj, null);/ / 3
                } catch(InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); }}}catch(ClassNotFoundException e) { e.printStackTrace(); }}}Copy the code

Create DiskClassLoader and pass in the path of the Class to be loaded. Create DiskClassLoader and pass in the path of the Class to be loaded. AppClassLoader takes care of the loading, so it makes no sense for us to define DiskClassLoader. Next, call Jobs’ say method by reflection in comment 3 and print the following result:

com.example.DiskClassLoader@4554617c
One more thingCopy the code

The DiskClassLoader was used to load the Class file, and the say method was executed correctly.

Afterword.

In this article, we will learn about ClassLoader in Java, including the types of ClassLoader, parent delegate mode, ClassLoader inheritance relationship, and custom ClassLoader. In order to better understand the next article to explain the Android ClassLoader.

An in-depth analysis of the Java ClassLoader principle


As a system