java中双亲委派详解

什么是双亲委派机制?

双亲委派机制(Parent Delegation Model)是Java类加载器(ClassLoader)加载类时的一种策略。它的核心思想是:当一个类加载器收到加载类的请求时,不会立即自己加载,而是先将请求委派给父类加载器处理。只有当父类加载器无法完成加载时,子类加载器才会尝试自己加载。

1. 双亲委派机制的核心思想

双亲委派机制的核心思想可以用一句话概括:“先让父类加载器尝试加载类,父类加载器无法完成时,才由子类加载器自己加载。”

这种机制是一种层次化的类加载模型,每个类加载器都有一个父类加载器(除了顶层的Bootstrap ClassLoader)。类加载器在加载类时,会按照以下步骤进行:

  1. 委派:将加载请求委派给父类加载器。

  2. 检查:父类加载器检查是否已经加载过该类。

  3. 加载:如果父类加载器无法加载,子类加载器才会尝试加载。


2. 类加载器的层次结构

Java中的类加载器是一个树形结构,分为以下几层:

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

  • 职责:加载JVM核心类库(如java.lang.*java.util.*等),这些类库位于JAVA_HOME/lib目录下。

  • 特点

    • 由C++实现,是JVM的一部分。

    • 没有父类加载器(它是类加载器树的根节点)。

    • 无法在Java代码中直接获取(返回null)。

(2)Extension ClassLoader(扩展类加载器)

  • 职责:加载JAVA_HOME/lib/ext目录下的类库,或者由java.ext.dirs系统属性指定的目录。

  • 特点

    • 由Java实现,对应的类是sun.misc.Launcher$ExtClassLoader

    • 父类加载器是Bootstrap ClassLoader。

(3)Application ClassLoader(应用程序类加载器)

  • 职责:加载用户类路径(ClassPath)上的类库。

  • 特点

    • 由Java实现,对应的类是sun.misc.Launcher$AppClassLoader

    • 父类加载器是Extension ClassLoader。

    • 是默认的类加载器,负责加载用户编写的类。

(4)自定义类加载器

  • 职责:用户可以通过继承ClassLoader类,实现自定义的类加载逻辑。

  • 特点

    • 父类加载器通常是Application ClassLoader。

    • 可以打破双亲委派机制,实现特定的类加载需求。


3. 双亲委派的工作流程

当一个类加载器收到加载类的请求时,会按照以下步骤执行:

  1. 检查是否已加载

    • 类加载器首先检查自己是否已经加载过该类。如果已经加载,直接返回该类。

  2. 委派给父类加载器

    • 如果未加载,将加载请求委派给父类加载器。

    • 父类加载器重复同样的过程,继续向上委派,直到Bootstrap ClassLoader。

  3. 父类加载器尝试加载

    • 如果父类加载器能够加载该类,加载过程结束。

    • 如果父类加载器无法加载,子类加载器才会尝试自己加载。

  4. 子类加载器尝试加载

    • 如果子类加载器也无法加载,抛出ClassNotFoundException


4. 双亲委派的代码实现

双亲委派机制的实现主要在ClassLoader类的loadClass方法中。以下是简化版的源码分析:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1. 检查是否已经加载过该类
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                // 2. 委派给父类加载器
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 如果父类加载器为null,说明是Bootstrap ClassLoader
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 父类加载器无法加载,忽略异常
            }

            // 3. 父类加载器无法加载时,自己尝试加载
            if (c == null) {
                c = findClass(name);
            }
        }
        // 4. 如果需要解析,解析该类
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

关键点:

  • findLoadedClass:检查是否已经加载过该类。

  • parent.loadClass:委派给父类加载器。

  • findClass:父类加载器无法加载时,自己尝试加载。


5. 双亲委派的优点

(1)避免重复加载

  • 由于类加载器会先委派给父类加载器,确保每个类只被加载一次,避免重复加载。

(2)安全性

  • 防止用户自定义的类替换核心类库中的类(如java.lang.String),确保核心类库的安全性。

(3)一致性

  • 保证同一个类在不同类加载器环境下的一致性。


6. 双亲委派的缺点

(1)灵活性不足

  • 在某些场景下,双亲委派机制可能不够灵活。例如:

    • 需要加载不同版本的类库。

    • 需要隔离不同模块的类加载环境。

(2)打破双亲委派

  • 在某些特殊场景下,可能需要打破双亲委派机制。例如:

    • Tomcat:每个Web应用使用独立的类加载器,避免类冲突。

    • OSGi:动态模块化系统,支持模块化加载和卸载。


7. 实际应用场景

(1)Tomcat的类加载器

  • Tomcat为每个Web应用提供了一个独立的类加载器(WebAppClassLoader),打破了双亲委派机制。

  • 这样做的目的是隔离不同Web应用的类加载环境,避免类冲突。

(2)OSGi框架

  • OSGi是一个动态模块化系统,每个模块(Bundle)有独立的类加载器。

  • OSGi通过复杂的类加载机制,支持模块的动态加载和卸载。

(3)SPI(Service Provider Interface)

  • Java的SPI机制(如JDBC)打破了双亲委派机制。

  • 例如,java.sql.Driver接口由Bootstrap ClassLoader加载,而具体的驱动实现(如com.mysql.cj.jdbc.Driver)由Application ClassLoader加载。


8. 总结

双亲委派机制是Java类加载的核心机制,通过层次化的委派方式,确保类的加载安全、一致且高效。它的主要优点包括避免重复加载、保证安全性和一致性,但在某些场景下可能不够灵活,需要打破双亲委派机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值