# Introduce

类加载器负责类的加载职责。

# JVM 内置三大类加载器

JVM 提供了三大内置的类加载器,不同的类加载器负责将不同的类加载到 JVM 内存中,且它们之间严格遵守着父委托的机制。如下图:

# Bootstrap 类加载器

Bootstrap 类加载也称为 “根加载器”,该类加载器是最顶层的加载器,由C++编写,负责虚拟机核心类库的加载。如:java.lang 包的加载。

# 验证 java.lang.String 所属类加载器

public class BootstrapClassloader {

    public static void main(String[] args) {
        System.out.println("Bootstrap:" + String.class.getClassLoader());
        System.out.println(System.getProperty("sun.boot.class.path"));
    }
}

控制台打印如下

# 根加载器无法获取到引用,所以为 null
Bootstrap:null
F:\programe-software\java\jdk\jre\lib\resources.jar;
F:\programe-software\java\jdk\jre\lib\rt.jar;
F:\programe-software\java\jdk\jre\lib\sunrsasign.jar;
F:\programe-software\java\jdk\jre\lib\jsse.jar;
F:\programe-software\java\jdk\jre\lib\jce.jar;
F:\programe-software\java\jdk\jre\lib\charsets.jar;
F:\programe-software\java\jdk\jre\lib\jfr.jar;
F:\programe-software\java\jdk\jre\classes

# Ext ClassLoader

Ext CLassLoader 翻译为 “扩展类加载器”,由 Java 实现的,主要用于加载 JAVA_HOME 下的 jre\lib\ext 子目录中的类库。

# 打印 Ext ClassLoader 加载的资源路径

public class ExtClassloader {

    public static void main(String[] args) {
        System.out.println(System.getProperty("java.ext.dirs"));
    }
}

控制台打印如下

F:\programe-software\java\jdk\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext

# Application ClassLoader

Application Classloader 负责加载 classpath 下的类库资源,如:项目开发时引入的第三方 jar 包。

# 打印 Application ClassLoader 加载的资源路径

public class ApplicationClassloader {
    public static void main(String[] args) {
        System.out.println(System.getProperty("java.class.path"));
        System.out.println(ApplicationClassloader.class.getClassLoader());
    }
}

# Custom Classloader

自定义类加载是 ClassLoader 的直接子类或者简介子类。

# 自定义一个类加载器

  • 1、自定义类加载器必须继承 ClassLoader
  • 2、自定义类加载器必须重写 findClass 方法

一个简单的类加载器如下

// 自定义类加载器必须是 ClassLoader 的直接或者间接子类
public class CustomClassload extends ClassLoader {

    // 定义默认的 class 存放路径
    private final static Path DEFAULT_CLASS_DIR = Paths.get("E:","classloader");

    private final Path classDir;

    public CustomClassload(){
        super();
        this.classDir = DEFAULT_CLASS_DIR;
    }

    public CustomClassload(String classDir){
        super();
        this.classDir = Paths.get(classDir);
    }

    public CustomClassload(String classDir,ClassLoader parent){
        super(parent);
        this.classDir = Paths.get(classDir);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 读取 class 的二进制数据
        byte[] classBytes = this.readClassBytes(name);
        // 如果数据为 null,或者没有读到任何信息,抛出 ClassNotFoundException 异常
        if(null == classBytes || classBytes.length == 0){
            throw new ClassNotFoundException("Can not load the class " + name);
        }
        // 调用 defineClass 方法定义 class
        return this.defineClass(name,classBytes,0,classBytes.length);
    }

    // 将 class 文件读入内存
    private byte[] readClassBytes(String name) throws ClassNotFoundException {
        String classPath = name.replace(".","/");
        Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));
        if(!classFullPath.toFile().exists()){
            throw new ClassNotFoundException("The class " + name + " not found.");
        }
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream()){
            Files.copy(classFullPath,baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new ClassNotFoundException("load the class  " + name + " occur error.");
        }
    }
}

测试类加载器

  • 1、写一个 Helloworld.java 并编译成 class 文件
public class Helloworld {
    static {
        System.out.println("Hello World Class is init.");
    }

    public String welcome(){
        return "Hello WOrld";
    }
}
  • 2、将编译后的 Helloworld.class 文件放入到类加载器的加载路径下,同时从项目中移除 Helloworld.java 文件

  • 3、测试类
public class CustomClassloadTest {
    public static void main(String[] args) 
    	throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 声明一个自定义 classloader
        CustomClassload classload = new CustomClassload();
        // 使用 classloader 加载 Helloworld
        Class<?> aClass = classload.loadClass("com.qguofeng.server.Helloworld");

        System.out.println(aClass.getClassLoader());

        // 注释以下代码,可以测试 class 被加载并不会马上初始化
        Object helloWorld = aClass.newInstance();
        System.out.println(helloWorld);
        Method welcomeMethod = aClass.getMethod("welcome");
        String result = welcomeMethod.invoke(helloWorld).toString();
        System.out.println("Result:" + result);
    }
}

# 双亲委派机制

类加载进行类加载时,会先一层层向上委托父级类加载器进行加载,如果父级类加载器加载在有调用的类加载器进行加载,如果还加载不到直接抛出 ClassNotFoundException 异常。

# 从源码看双亲委派机制

入口: ClassLoader#loadClass

public abstract class ClassLoader {
	protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    	// ① 父类加载器存在,递归调用
                        c = parent.loadClass(name, false);
                    } else {
                    	// 没有父类加载器,则交给 g根加载器进行加载。
                        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 stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

调用 UML 如下

递归循环,由父类进行加载,如果都没有最后才自己加载,如果都没有加载到抛出 ClassNotFoundException

# 上述自定义类加载扩展

在上面的自定义类加载器中,为了保证自定义类加载器被加载,需要将测试类 Helloworld.java 编译成 class 文件后,将其从项目中删除,反之被 Application ClassLoader 加载到。

如何在不从项目中删除 Helloworld.java 的情况下, 保证类被我们自定义的类加载器加载。

# 方式一:构造自定义类加载器,指定父类加载器为 null

当自定义类加载的父类加载器为null 时,会直接使用 Bootstrap ClassloadCustom Classload 进行类加载。而项目中的类不会被 Bootstrap Classload 加载,只会被 Custom Classload 加载。

# 方式二:构造自定义类加载,指定父类加载器指定为 Ext Classloader

项目中的类只可能被 Application Classloader 加载,Ext ClassloaderBootstrap Classloader 都不会加载到项目中的类,最终 Helloworld.java只会被 Custom Classload 加载。

精彩内容推送,请关注公众号!
最近更新时间: 4/16/2020, 8:15:03 PM