基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Java双亲委派模型深度解析

知识点图片

讲解Java的双亲委派模型:类加载请求会逐级向上委派,以保证核心库安全并避免类的重复加载。

我们来深入浅出地讲解一下Java的双亲委派模型(Parents Delegation Model)。

这是一个非常核心且重要的JVM概念,理解它有助于你明白Java类是如何被加载和隔离的,也能帮你解决很多类似“jar包冲突”的问题。


1. 什么是双亲委派模型?(What)

一句话概括:当一个类加载器(ClassLoader)收到加载类的请求时,它不会自己先去尝试加载,而是先把这个请求委派给它的父类加载器去完成。每一层的类加载器都是如此,因此所有的加载请求最终都会传送到顶层的启动类加载器(Bootstrap ClassLoader)。只有当父加载器无法完成这个加载请求(在它的搜索范围内没有找到所需的类)时,子加载器才会自己去尝试加载。

一个生活化的比喻:
想象一个富三代想买一个玩具。

  1. 他不会自己直接去店里买,而是先问他爸爸:“爸,你有这个玩具吗?”
  2. 他爸爸也不会自己去找,而是先问他爷爷:“爸,你有这个玩具吗?”
  3. 爷爷是家族的创始人,他先在自己的宝库里找。
    • 如果找到了,就直接拿出来给孙子。交易结束。
    • 如果没找到,他会告诉儿子:“我没有。”
  4. 这时,儿子才会在自己的仓库里找。
    • 如果找到了,就拿给儿子。
    • 如果还没找到,他会告诉儿子:“我也没有。”
  5. 最后,这个富三代才会自己去玩具店里买。如果连玩具店都买不到,那就真的没有了(抛出 ClassNotFoundException)。

在这个比喻中:

  • 爷爷 -> 启动类加载器 (Bootstrap ClassLoader)
  • 爸爸 -> 扩展类加载器 (Extension ClassLoader / Platform ClassLoader)
  • 富三代 -> 应用程序类加载器 (Application ClassLoader)
  • 玩具 -> 需要加载的类 (.class 文件)

2. 为什么需要双亲委派模型?(Why)

这种设计主要有两个目的:

a. 避免类的重复加载

一个类,由不同的类加载器加载,在JVM中会被认为是两个完全不同的类。例如,如果你自己写了一个 java.lang.String 类,并用两个不同的加载器加载了系统的String类和你自己写的String类,那么JVM内部就会存在两个String类的实例。

双亲委派模型确保了一个类只会被加载一次。因为所有的加载请求都会最终汇集到顶层的加载器,比如 java.lang.Object,它最终一定是由启动类加载器(Bootstrap ClassLoader)加载的,这就保证了任何地方引用到的 Object 类都是同一个。

b. 保证Java核心库的安全性

这是最重要的原因。如果没有双亲委派,那么任何人都可以编写一个自己的 java.lang.String 类,并在其中植入恶意代码。当JVM需要加载 String 类时,如果恰好加载了你写的这个恶意版本,那么整个Java应用的安全体系都会被摧毁。

有了双亲委派模型:

  1. JVM要加载 java.lang.String
  2. 请求被发送到应用程序类加载器。
  3. 它委派给父类(扩展类加载器)。
  4. 扩展类加载器再委派给父类(启动类加载器)。
  5. 启动类加载器在自己的搜索路径(如 rt.jar)中找到了官方的 java.lang.String,并加载它。
  6. 加载成功后,这个类被返回。你编写的那个恶意 String 类根本没有机会被加载。

3. 双亲委派模型是如何工作的?(How)

这需要先了解Java中的几种主要类加载器:

  1. 启动类加载器 (Bootstrap ClassLoader)

    • 由C++实现,是JVM自身的一部分。
    • 负责加载Java最核心的类库,如 <JAVA_HOME>/lib 目录下的 rt.jar(Java 8及以前)或 java.base 模块(Java 9及以后)。
    • 它没有父加载器,是所有加载器的“祖宗”。
  2. 扩展类加载器 (Extension ClassLoader) - 在Java 9中被 平台类加载器 (Platform ClassLoader) 取代。

    • 由Java实现。
    • 负责加载 <JAVA_HOME>/lib/ext 目录下的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库。
    • 它的父加载器是启动类加载器。
  3. 应用程序类加载器 (Application ClassLoader)

    • 也叫系统类加载器(System ClassLoader)。
    • 由Java实现。
    • 负责加载用户类路径(Classpath)上所指定的类库。我们自己写的Java代码,默认就是由它来加载的。
    • 它的父加载器是扩展类加载器。
  4. 自定义类加载器 (Custom ClassLoader)

    • 开发者可以根据自己的需求编写,可以用来实现类的热部署、代码加密等。
    • 它的父加载器通常是应用程序类加载器。

加载流程(源码解读)

这个模型的实现核心在 java.lang.ClassLoaderloadClass() 方法中。下面是简化后的伪代码:

java
public Class<?> loadClass(String name) throws ClassNotFoundException {
    // 1. 检查这个类是否已经被加载过了
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            // 2. 如果父加载器不为null,就委派给父加载器去加载
            if (parent != null) {
                c = parent.loadClass(name);
            } else {
                // 如果父加载器为null(说明父加载器是Bootstrap ClassLoader)
                // 委派给Bootstrap ClassLoader去加载
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 3. 如果父加载器和启动类加载器都找不到这个类
            // 此时会捕获异常,但什么都不做,流程继续向下
        }

        if (c == null) {
            // 4. 如果到这里c还是null,说明所有父加载器都加载失败了
            // 此时,才轮到自己调用 findClass() 方法进行加载
            c = findClass(name);
        }
    }
    return c;
}

关键点:

  • loadClass():负责整个委派逻辑。
  • findClass():负责实际的类加载(从文件、网络等地方读取字节码,并定义为Class对象)。如果你要自定义类加载器,推荐重写 findClass() 方法,而不是 loadClass() 方法,这样可以保留双亲委派的机制。

4. 如何打破双亲委派模型?

虽然双亲委派模型很优秀,但在某些场景下,它并不适用。打破它也是Java生态中常见的操作。

为什么要打破?
一个典型的例子是 Tomcat。Tomcat是一个Web容器,它可能需要部署多个独立的Web应用。

  • 应用A可能依赖 library-v1.0.jar
  • 应用B可能依赖 library-v2.0.jar

如果都使用应用程序类加载器,根据双亲委派,只有一个版本的library会被加载,这会导致另一个应用出错。所以,Tomcat需要实现应用之间的类库隔离

Tomcat的实现方式:
Tomcat为每个Web应用创建了一个专门的类加载器(WebAppClassLoader)。这个加载器重写了 loadClass() 方法,其逻辑与双亲委派相反

  1. 先在自己的仓库(WEB-INF/classesWEB-INF/lib)中查找类。
  2. 如果找到了,就自己加载,不再向上委派。
  3. 如果自己找不到,才委派给父加载器(Application ClassLoader等)去加载。

这样就保证了每个Web应用使用的类库都是自己私有的,实现了隔离。

其他例子还包括JNDI、OSGi等技术,它们都因自身需要而对双亲委派模型做了调整。

总结

特性 描述
核心思想 向上委派,向下加载。先问父亲,不行再自己上。
加载器层次 Bootstrap -> Platform(Extension) -> Application -> Custom
主要优点 安全性(保护核心库)、唯一性(避免重复加载)
实现代码 ClassLoader.loadClass() 方法
自定义方式 推荐重写 findClass(),保留委派逻辑
打破场景 需要实现隔离和热部署的场景,如Tomcat、OSGi
00:00
00:00