Bootstrap

Java双亲委派模型

双亲委派模型

双亲委派模型(Parents Delegation Model)是 Java 虚拟机(JVM)加载类的一种层次化模型。在这个模型中,类加载器(ClassLoader)之间存在一种层次关系,当一个类加载器收到加载类的请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。

类加载器层次结构

启动类加载器(Bootstrap ClassLoader):它是最顶层的类加载器,主要负责加载 Java 的核心库(如 java.lang 包中的类),这个加载器是用本地代码(C/C++)实现的,它没有父加载器,是虚拟机自身的一部分。例如,像Object类、String类这些最基础的 Java 类都是由启动类加载器加载的。
扩展类加载器(Extension ClassLoader):它的父加载器是启动类加载器。主要负责加载 Java 的扩展库,即jre/lib/ext目录下的类库,或者由java.ext.dirs系统变量指定路径中的类库。比如,一些 Java 的高级特性或者标准库的扩展可能会被这个加载器加载。
应用程序类加载器(Application ClassLoader):也称为系统类加载器,它的父加载器是扩展类加载器。它负责加载用户类路径(classpath)上的类库,这是我们在日常开发中最常接触到的类加载器。我们自己编写的 Java 类通常都是由这个加载器加载的。
在这里插入图片描述

优缺点

优点

安全性高
  • 防止核心 API 被篡改:Java 核心类库(如 java.lang 包中的类)是由启动类加载器加载的。这保证了 Java 的基础类(如 Object、String 等)的唯一性和安全性。例如,恶意用户无法简单地自定义一个java.lang.Object类来替换掉官方的核心类。因为在加载用户自定义的所谓 “Object” 类时,加载请求会先委派给启动类加载器,而启动类加载器已经加载了真正的java.lang.Object类,就不会再加载用户自定义的这个可能带有恶意的类,从而保证了整个 Java 程序运行环境的安全稳定。
  • 信任基础类库:由于核心类库是由特定的启动类加载器加载,这些类库经过了严格的测试和验证。开发者在使用这些基础类库(如集合类、线程相关类等)时,可以信任它们的功能正确性,这有助于构建可靠的 Java 应用程序。
避免类的重复加载
  • 提高加载效率:类加载请求是按照层次结构进行委派的。在整个类加载过程中,一个类只会被加载一次。例如,如果扩展类加载器已经加载了某个类,应用程序类加载器就不会再重复加载这个类。这避免了因多次加载同一类而导致的资源浪费,提高了类加载的效率。
  • 维护类的一致性:这种机制有助于维护整个 Java 程序中类的一致性。假设没有双亲委派模型,多个类加载器可能会加载同一个类的不同版本,这会导致类型混淆等问题。而双亲委派模型通过层次结构的委派和加载控制,保证了在一个 Java 虚拟机中,一个类只有一个定义能够被加载,使得类的版本在整个程序运行环境中保持一致。
类加载的层次化管理
  • 职责清晰:不同的类加载器有明确的职责范围。启动类加载器负责加载核心类库,扩展类加载器负责加载扩展库,应用程序类加载器负责加载应用程序类路径上的类。这种层次化的管理方式使得类加载过程更加清晰有序,便于开发者理解和维护。
  • 便于扩展和定制:在需要扩展类加载功能时,开发者可以通过继承现有的类加载器并遵循双亲委派模型来实现。例如,开发一个自定义的类加载器来加载特定目录下的插件类,通过正确地设置其在类加载层次结构中的位置(作为应用程序类加载器的子加载器等),可以方便地将插件类融入到整个 Java 应用程序的类加载体系中。

缺点

灵活性受限
  • 加载自定义类的复杂性:对于一些特殊情况,如需要加载自定义的、与 Java 核心类库同名的类时,双亲委派模型会带来一些不便。因为按照模型规则,加载请求会先委派给启动类加载器等上层加载器,而这些上层加载器通常不会加载不符合其职责范围的自定义类。例如,开发者想要在自己的应用程序中使用一个自定义的java.util.Date类(可能是对日期类的特殊扩展),但由于双亲委派模型,这个类很难按照常规方式加载,需要采取一些特殊的策略(如自定义类加载器的加载路径和加载顺序等)来绕过双亲委派模型的限制。
  • 难以动态加载变化类:在某些动态加载类的场景下,如热部署(在应用程序运行过程中更新类的定义)或者在一些需要频繁更换类版本的场景中,双亲委派模型可能会成为阻碍。因为它的加载机制相对固定,难以快速适应类的动态变化。例如,在一个 Web 应用服务器中,当需要更新一个已经加载的类(如一个 Servlet 类)时,由于双亲委派模型的存在,可能需要复杂的重新加载和卸载机制,并且容易出现类版本冲突等问题。
对类加载器的层次依赖过强
  • 紧密耦合的层次关系:双亲委派模型建立了一种紧密的类加载器层次关系。如果在这个层次结构中的某个类加载器出现问题(如加载功能故障或者加载策略需要修改),可能会影响到整个类加载过程。例如,如果扩展类加载器因为错误配置而无法正常工作,那么不仅它自身负责的扩展类加载会受到影响,还可能会影响到应用程序类加载器对某些类的加载,因为应用程序类加载器依赖于扩展类加载器的委派反馈。
  • 限制类加载器的创新设计:这种强层次关系在一定程度上限制了类加载器设计的灵活性和创新性。开发人员在设计新的类加载器时,需要严格遵循双亲委派模型的层次规则,很难突破这种传统的加载模式去尝试一些新的、可能更高效或者更适合特殊场景的类加载方式。

不同JVM版本中的变化

Java 1.0 - 1.1 时代:基本的双亲委派模型初步建立

  • 在早期的 Java 版本(如 Java 1.0 - 1.1)中,双亲委派模型开始初步形成。这个时期,JVM 已经有了启动类加载器、扩展类加载器和应用程序类加载器这样的基本分层结构。
  • 启动类加载器主要负责加载 Java 核心类库,这些类库对于 Java 程序的基本运行是至关重要的。例如,像java.lang包中的类(如Object、String等)都是由启动类加载器加载的。
  • 扩展类加载器负责加载 Java 的扩展库,为 Java 程序提供了一些额外的功能支持。
  • 应用程序类加载器负责加载用户自定义的类,也就是在应用程序的classpath路径下的类。这种分层结构奠定了双亲委派模型的基础,使得类加载过程有了初步的秩序,并且在一定程度上保证了 Java 核心类库的安全性和类加载的效率。

Java 2 - Java 7:双亲委派模型的巩固和优化

  • 类加载器层次更加清晰明确:例如在处理不同来源的类库加载时(如系统类库、扩展类库和用户类库),严格遵循双亲委派的流程。
  • 安全机制加强:例如,恶意用户想要加载一个自定义的java.lang.Object类来干扰 Java 程序的正常运行,双亲委派模型会阻止这种情况的发生。因为在加载该类时,请求会先委派给启动类加载器,而启动类加载器已经加载了真正的Object类,不会再加载用户自定义的版本。
  • 性能优化:在遵循双亲委派模型的基础上,通过缓存已经加载的类等机制,减少了不必要的类加载操作。当一个类已经被加载后,后续的加载请求可以直接从缓存中获取,提高了整体的运行效率。

Java 8 - 至今:在模块化和动态特性下的调整

  • 模块系统对双亲委派模型的影响:Java 9 引入了模块系统。例如,在模块内部,类的加载仍然遵循双亲委派原则,但是模块之间的类加载和访问需要考虑模块的边界和依赖关系。一些类可能因为模块的封装性而只能在特定模块内部被加载和访问,这对传统的双亲委派模型在跨模块类加载方面提出了新的要求。
  • 动态类加载的变化:例如,在动态代理中,生成的代理类的加载可能会涉及到多个类加载器之间的协调。JVM 需要确保在动态加载这些代理类时,仍然遵循双亲委派模型的安全性和一致性原则,同时又能满足动态加载的灵活性要求。

双亲委派模型被破坏带来的风险

  • 类的一致性被破坏
    • 版本冲突
    • 类型不匹配
  • 安全性受损
    • 核心类库被篡改
    • 权限控制失效
  • 类加载效率降低
    • 重复加载问题
    • 混乱的加载顺序
破坏双亲委派模型的示例代码
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class CustomClassLoader extends URLClassLoader {

    public CustomClassLoader(URL[] urls) {
        super(urls);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 先尝试自己加载
        try {
            return findClass(name);
        } catch (ClassNotFoundException e) {
            // 如果自己加载失败,再调用父类的加载方法
            return super.loadClass(name, resolve);
        }
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 自定义类加载器加载的路径
        File file = new File("path/to/custom/classes");
        URL url = file.toURI().toURL();
        URL[] urls = new URL[]{url};
        CustomClassLoader customClassLoader = new CustomClassLoader(urls);

        // 加载自定义路径下的类
        Class<?> customClass = customClassLoader.loadClass("com.example.CustomClass");
        Object instance = customClass.newInstance();

        // 调用自定义类的方法
        Method method = customClass.getMethod("customMethod");
        method.invoke(instance);
    }
}

Java实现源码

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 首先检查这个类是否已经被加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent!= null) {
                    // 如果有父加载器,调用父加载器的loadClass方法
                    c = parent.loadClass(name, resolve);
                } else {
                    // 如果没有父加载器(如启动类加载器),调用本地方法加载核心类库
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果父加载器无法加载这个类,自己尝试加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;