【Java】JVM-类加载器(Class Loader)

Posted by 西维蜀黍 on 2019-03-06, Last Modified on 2021-09-21

类加载器(Class Loader)

在 Java 中,默认提供的三种类加载器,分别是 BootStrapClassLoader(启动类加载器)、ExtClassLoader(扩展类加载器)、AppClassLoader(应用程序类加载器)。

同时,我们也可以定义自己的类加载器 (CustomClassLoader),那么它的 parent 肯定就是 AppClassLoader(应用程序类加载器)了。类加载器的这种层次关系称为双亲委派模型(Parents Delegation Model)

  • BootStrapClassLoader(启动类加载器):它由 C++ 实现的,在 Java 程序中不能显式地获取到。它负责加载存放在 %JAVA_HOME%/jre/lib%JAVA_HOME%/jre/classes 以及 -Xbootclasspath 参数指定的路径中的类。
  • ExtClassLoader(扩展类加载器):它是由sun.misc.Launcher$ExtClassLoader 实现,负责加载 %JAVA_HOME%/jre/lib/ext 路径下的所有 classes 目录以及java.ext.dirs 系统变量指定的路径中的类库。
  • AppClassLoader(应用程序类加载器):由sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类路径(ClassPath)中的类,开发者可以直接使用该类加载器。一般来说,开发者自定义的类就是由应用程序类加载器加载的。

分析

ExtClassLoader(扩展类加载器) 作为类加载器,但它也是一个Java类,是由 BootStrapClassLoader(启动类加载器)来加载的,所以,ExtClassLoader(扩展类加载器)的 parent 是 BootStrapClassLoader(启动类加载器)。但是由于 BootStrapClassLoader(启动类加载器)是 C++ 实现的,我们通过 ExtClassLoader.getParent 获取到的是 null。同样地,AppClassLoader(应用程序类加载器)是由 ExtClassLoader(扩展类加载器)加载,AppClassLoader(应用程序类加载器)的parent是 ExtClassLoader(扩展类加载器)。

类加载器加载类的隔离问题

每个类加载器都有一个自己的命名空间,用来标示它加载的类。

当一个类加载器加载一个类时,它会通过保存在命名空间里的**类全局限定名(Fully Qualified Class Name)**进行搜索,来检测这个类是否已经被加载了。

JVMDalvik 对类唯一的识别是 ClassLoader id + PackageName + ClassName,所以一个运行程序中是有可能存在两个包名和类名完全一致的类的。并且如果这两个”类”不是由同一个类加载器加载的时,是无法将一个类的实例强转为另外一个类的,这就是类加载器隔离。

双亲委托 是类加载器类一致问题的一种解决方案,也是 Android 差价化开发和热修复的基础。

自定义类加载器

一般地,在ClassLoader方法的loadClass方法中已经给开发者实现了双亲委派模型,在自定义类加载器的时候,只需要复写findClass方法即可。

public class CustomClassLoader extends ClassLoader {

    private String root;

    public CustomClassLoader(String root) {
        this.root = root;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String name) {
        String fileName = root + File.separatorChar
                + name.replace('.', File.separatorChar)
                + ".class";
        try {
            InputStream ins = new FileInputStream(fileName);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 1024;
            byte[] buffer = new byte[bufferSize];
            int length;
            while ((length = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

新建一个类com.xiao.U,编译成class文件,放到桌面,来测试一下:

public class Test {
    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader("C:\\Users\\PC\\Desktop");
        try {
            Class clazz = customClassLoader.loadClass("com.xiao.U");
            Object o = clazz.newInstance();
            System.out.println(o.getClass().getClassLoader());
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }
}

打印结果:

CustomClassLoader@1540e19d

自定义类加载器在可以实现服务端的热部署,在移动端比如android也可以实现热更新。

Reference