学习类加载器,避免天天上线

110 阅读3分钟

类加载器的分类

  • 启动类加载器

这个类加载器是由C++语言实现的,是虚拟机自身的一部分。负责加载存放<JAVA_HOME>\lib目录或者被-Xbootclasspath参数所指定的路径中存放的,而且能被Java虚拟机所识别的(如rt.jar,名称不符合的类库即使在lib下也不会被加载)类库会被加载到虚拟机内存中。

public static void main(String[] args) {
    System.out.println(String.class.getClassLoader()); // null
    String paths = System.getProperty("sun.boot.class.path");
    Arrays.stream(paths.split(":")).forEach(System.out::println); // 获取当前加载的jar包名称
}
  • 扩展类加载器

这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。

  • 应用程序类加载器

这个类加载器由sun.misc.Launcher$AppClassLoader来实现。 它负责加载用户类路径(Classpath)上所有的类库,一般情况下,这个是程序中默认的类加载器。

双亲委派模型

如果一个类加载器要加载一个类,并不选择自己直接加载,而是会先选择让自己的父加载器去加载,如果父加载器仍有父亲,则会一直向上委托,直到找到启动类加载器(Bootstrap Class Loader)。如果父加载器加载成功,则返回true,如果父加载不负责加载此类,则有子加载器自己完成加载。

该模式的优势:

  • 避免类被重复加载,或者说有迹可寻。
  • 保证核心API不会被随意替换。

自定义类加载器

自定义类加载器可以通过继承URLClassLoader来重写其中的loadClass即可完成。

import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: woniu
 * @Date: 2023/4/16 10:26 AM
 **/
public class MyClassLoader extends URLClassLoader {

    private URL[] urls;

    public MyClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
        this.urls = urls;
    }

    public MyClassLoader(URL[] urls) {
        super(urls);
        this.urls = urls;
    }

    public MyClassLoader() {
        super(new URL[0]);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (null != urls && urls.length > 0) {
            Class<?> clazz = null;
            try {
                // 优先从当前classloader查找,如果无法完成再从父加载器中查找。
                clazz = findClass(name);
                if (clazz != null) {
					return clazz;
                }
            } catch (Exception e) {

            }
        }
        return super.loadClass(name);
    }
	
    @Override
    public URL getResource(String name) {
        URL url = findResource(name);
        if (url == null) {
            url = super.getResource(name);
        }
        return url;
    }
}

上述实现中,重写了getResource方法,一般讲解关于classloader都不会重写该方法,因为我们的业务场景中,加载的三方jar包中,包含解析resource下的配置文件,因此关于resource文件也需要重写,每次优先在本jar内完成查找,如果不存在,再使用父加载器的。

关于自定义类加载,如果指定的父加载器为ExtClassLoader,同时加载了一个Demo的类,同时,AppClassLoader也加载了该类,那么这两个类不是同一个类,判断是否是同一个类的前提,这两个类必须是来自同一个ClassLoader

场景

目前业务中使用的场景:

  • 定义接口,由不同的业务方提供具体的实现,且支持随时更新的能力(我们负责任务调度,具体任务的执行逻辑是由业务方自己实现,如有更新将jar包上传到指定路径,次日加载完成新的能力升级)。
  • 依赖三方jar更新频繁,这个jar包主要负责协议转换,且频繁调整更新。