JAVA双亲委派机制详解及实际应用场景

 更新时间:2025年08月20日 08:44:24   作者:嗜好ya  
双亲委派机制是Java类加载器加载类时的一种策略,这篇文章主要介绍了JAVA双亲委派机制的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

什么是双亲委派机制

双亲委派机制(Parents Delegation Model)是JVM中类加载器的一种工作机制。当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。只有当父类加载器无法完成加载请求时,子类加载器才会尝试自己去加载。

这种机制确保了Java核心API的类不会被随意替换,维护了Java运行环境的安全性和稳定性。

类加载器的层级结构

Java中的类加载器按照层级关系分为以下几种:

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

  • 位置:JVM内部实现,由C++代码实现
  • 作用:加载Java核心类库(如java.lang.*java.util.*等)
  • 路径$JAVA_HOME/lib目录下的类库
  • 特点:最顶层的类加载器,没有父类加载器

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

  • 位置sun.misc.Launcher$ExtClassLoader
  • 作用:加载扩展类库
  • 路径$JAVA_HOME/lib/ext目录下的类库
  • 父类加载器:启动类加载器

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

  • 位置sun.misc.Launcher$AppClassLoader
  • 作用:加载应用程序类路径(ClassPath)上的类
  • 路径:环境变量ClassPath指定的路径
  • 父类加载器:扩展类加载器
  • 特点:也称为系统类加载器

4. 自定义类加载器(Custom ClassLoader)

  • 作用:用户根据需要自定义的类加载器
  • 父类加载器:通常是应用程序类加载器
// 查看类加载器层级结构的示例代码
public class ClassLoaderHierarchy {
    public static void main(String[] args) {
        // 获取当前类的类加载器
        ClassLoader classLoader = ClassLoaderHierarchy.class.getClassLoader();
        System.out.println("当前类的类加载器:" + classLoader);
        
        // 获取父类加载器
        ClassLoader parentClassLoader = classLoader.getParent();
        System.out.println("父类加载器:" + parentClassLoader);
        
        // 获取祖父类加载器
        ClassLoader grandParentClassLoader = parentClassLoader.getParent();
        System.out.println("祖父类加载器:" + grandParentClassLoader);
        
        // 输出结果:
        // 当前类的类加载器:sun.misc.Launcher$AppClassLoader@2a139a55
        // 父类加载器:sun.misc.Launcher$ExtClassLoader@15db9742
        // 祖父类加载器:null (Bootstrap ClassLoader由C++实现,在Java中显示为null)
    }
}

双亲委派机制的工作原理

双亲委派机制的工作流程如下:

  1. 接收加载请求:类加载器接收到类加载请求
  2. 向上委派:不立即加载,而是委派给父类加载器
  3. 递归委派:父类加载器继续向上委派,直到启动类加载器
  4. 尝试加载:启动类加载器尝试加载类
  5. 向下返回:如果加载失败,返回给子类加载器尝试加载
  6. 最终加载:直到某个类加载器成功加载类或全部失败
graph TD
    A[自定义类加载器] --> B[应用程序类加载器]
    B --> C[扩展类加载器]
    C --> D[启动类加载器]
    D --> E{能否加载?}
    E -->|能| F[加载完成]
    E -->|不能| G[委派给子类加载器]
    G --> H{扩展类加载器能否加载?}
    H -->|能| I[加载完成]
    H -->|不能| J[委派给子类加载器]
    J --> K{应用程序类加载器能否加载?}
    K -->|能| L[加载完成]
    K -->|不能| M[委派给子类加载器]
    M --> N{自定义类加载器能否加载?}
    N -->|能| O[加载完成]
    N -->|不能| P[抛出ClassNotFoundException]

为什么需要双亲委派机制

1. 避免类的重复加载

如果没有双亲委派机制,每个类加载器都可能加载同一个类,导致内存中存在多个相同的类对象。

2. 保证Java核心API的安全性

防止核心API被恶意替换。例如,如果有人自定义了一个java.lang.String类,通过双亲委派机制,最终会由启动类加载器加载JDK中的String类,而不是用户自定义的类。

3. 保证类的唯一性

在JVM中,类的唯一性是由类加载器和类的全限定名共同决定的。双亲委派机制确保了同一个类只会被同一个类加载器加载一次。

双亲委派机制的源码分析

让我们来看看ClassLoader类中loadClass方法的实现:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 首先检查该类是否已经被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 如果有父类加载器,委派给父类加载器加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 如果没有父类加载器,说明是启动类加载器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果父类加载器抛出ClassNotFoundException
                // 说明父类加载器无法完成加载请求
            }

            if (c == null) {
                // 如果父类加载器无法加载,则调用自己的findClass方法进行加载
                long t1 = System.nanoTime();
                c = findClass(name);
                
                // 记录加载时间统计
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

双亲委派机制的破坏

虽然双亲委派机制很重要,但在某些场景下需要被破坏:

1. 自定义类加载器

通过重写loadClass方法来改变类加载的行为:

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
        throws ClassNotFoundException {
        // 首先检查是否已经加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            // 对于自定义的类,直接由当前类加载器加载
            if (name.startsWith("com.example.")) {
                c = findClass(name);
            } else {
                // 其他类仍然遵循双亲委派
                c = super.loadClass(name, resolve);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 实现自定义的类加载逻辑
        byte[] classData = loadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }
    
    private byte[] loadClassData(String name) {
        // 从自定义位置加载类的字节码
        // 这里可以从网络、数据库等位置加载
        return null; // 简化示例
    }
}

2. 线程上下文类加载器

在某些情况下,父类加载器需要加载由子类加载器加载的类,这时可以使用线程上下文类加载器:

public class ContextClassLoaderExample {
    public static void main(String[] args) {
        // 获取当前线程的上下文类加载器
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        System.out.println("上下文类加载器:" + contextClassLoader);
        
        // 设置自定义的上下文类加载器
        Thread.currentThread().setContextClassLoader(new CustomClassLoader());
        
        // 在某些框架中,会使用上下文类加载器来加载类
        // 例如:JDBC驱动加载、Spring容器等
    }
}

3. 热替换和热部署

在开发环境中,为了实现热替换功能,需要破坏双亲委派机制:

public class HotSwapClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
        throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            // 对于需要热替换的类,每次都重新加载
            if (isHotSwapClass(name)) {
                c = findClass(name);
            } else {
                c = super.loadClass(name, resolve);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
    
    private boolean isHotSwapClass(String name) {
        // 判断是否是需要热替换的类
        return name.startsWith("com.example.hotswap");
    }
}

实际应用场景

1. Web应用服务器

Tomcat等Web服务器为了实现应用隔离,每个Web应用都有自己的类加载器:

// Tomcat的类加载器层级结构
// Bootstrap ClassLoader
//     |
// System ClassLoader
//     |
// Common ClassLoader
//     |
// Catalina ClassLoader  Shared ClassLoader
//                           |
//                      WebApp ClassLoader

2. OSGi框架

OSGi框架完全破坏了双亲委派机制,实现了网状的类加载器结构。

3. 模块化系统

Java 9的模块系统也对双亲委派机制进行了一定的改进。

总结

双亲委派机制是Java类加载器的核心机制,它具有以下特点:

优点:

  • 避免类的重复加载
  • 保证Java核心API的安全性
  • 维护类的唯一性

缺点:

  • 在某些场景下过于严格,需要被破坏
  • 可能导致类加载的性能问题

适用场景:

  • 大部分标准Java应用
  • 需要保证类加载安全性的场景

破坏场景:

  • 自定义类加载器
  • 热替换和热部署
  • 模块化系统
  • Web应用服务器

到此这篇关于JAVA双亲委派机制的文章就介绍到这了,更多相关JAVA双亲委派机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot前端传递数组后端接收两种常用的方法

    SpringBoot前端传递数组后端接收两种常用的方法

    这篇文章主要给大家介绍了关于SpringBoot前端传递数组后端接收两种常用的方法,文中通过代码介绍的非常详细,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-04-04
  • 使用Hutool编写生成随机数的工具类

    使用Hutool编写生成随机数的工具类

    Hutool 是一个 Java 工具类库,提供了丰富的工具方法,其中 RandomUtil 是 Hutool 中用于生成随机数的工具类,下面我们来看看它的具体使用吧
    2025-02-02
  • Java通俗易懂系列设计模式之策略模式

    Java通俗易懂系列设计模式之策略模式

    这篇文章主要介绍了Java通俗易懂系列设计模式之策略模式,对设计模式感兴趣的同学,一定要看一下
    2021-04-04
  • PostgreSQL Docker部署+SpringBoot集成方式

    PostgreSQL Docker部署+SpringBoot集成方式

    本文介绍了如何在Docker中部署PostgreSQL和pgadmin,并通过SpringBoot集成PostgreSQL,主要步骤包括安装PostgreSQL和pgadmin,配置防火墙,创建数据库和表,以及在SpringBoot中配置数据源和实体类
    2024-12-12
  • 详解FutureTask如何实现最大等待时间

    详解FutureTask如何实现最大等待时间

    这篇文章主要为大家详细介绍了如何从源码中了解FutureTask实现最大等待时间的方法,文中的示例代码讲解详细,感兴趣的可以了解一下
    2023-03-03
  • Spring零基础到进阶之使用方法详解

    Spring零基础到进阶之使用方法详解

    Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能
    2022-07-07
  • jdk动态代理和cglib动态代理详解

    jdk动态代理和cglib动态代理详解

    本篇文章主要介绍了深度剖析java中JDK动态代理机制 ,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定一组接口及目标类对象就能动态的获得代理对象
    2021-07-07
  • java随机事件分发器示例

    java随机事件分发器示例

    这篇文章主要介绍了java随机事件分发器示例,需要的朋友可以参考下,功能需求和代码实现在下面
    2014-03-03
  • JavaWeb Maven详解相关配置

    JavaWeb Maven详解相关配置

    这篇文章主要介绍了使用maven架构管理开发的相关配置,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Java中Date数据类型的数值转换方式

    Java中Date数据类型的数值转换方式

    这篇文章主要介绍了Java中Date数据类型的数值转换方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07

最新评论