首页 > 其他分享 >浅析双亲委派机制

浅析双亲委派机制

时间:2024-10-29 20:32:26浏览次数:4  
标签:委派 loadClass ClassLoader 双亲 findClass 浅析 加载

双亲委派机制

1)什么是双亲委派

虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢?这就不得不提到"双亲委派机制"。

首先,我们需要知道的是,Java语言系统中支持以下4种类加载器:

  • Bootstrap ClassLoader 启动类加载器:主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
  • Extention ClassLoader 标准扩展类加载器:主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
  • Application ClassLoader 应用类加载器:主要负责加载当前应用的classpath下的所有类。
  • User ClassLoader 用户自定义类加载器:用户自定义的类加载器,可加载指定路径的class文件。

也就是说,一个用户自定义的类,如com.hollis.ClassHollis 是无论如何也不会被Bootstrap和Extention加载器加载的

这四种类加载器之间,是存在着一种层次关系的,如下图

一般认为上一层加载器是下一层加载器的父加载器,那么,除了BootstrapClassLoader之外,所有的加载器都是有父加载器的。

那么,所谓的双亲委派机制,指的就是:当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。

2)为什么需要双亲委派

因为类加载器之间有严格的层次关系,那么也就使得Java类也随之具备了层次关系。比如一个定义在java.lang包下的类,因为它被存放在rt.jar之中,所以在被加载过程汇总,会被一直委托到Bootstrap ClassLoader,最终由Bootstrap ClassLoader所加载。

而一个用户自定义的com.hollis.ClassHollis类,他也会被一直委托到Bootstrap ClassLoader,但是因为Bootstrap ClassLoader不负责加载该类,那么会在由Extention ClassLoader尝试加载,而Extention ClassLoader也不负责这个类的加载,最终才会被Application ClassLoader加载。

这种机制有几个好处。

首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。

另外,通过双亲委派的方式,还保证了安全性。假如我们自己编写一个类java.util.Object,它的实现可能有一定的危险性或者隐藏的bug。而我们知道Java自带的核心类里面也有java.util.Object,如果JVM启动的时候先行加载的是我们自己编写的java.util.Object,那么就有可能出现安全问题!

3)加载器之间的关系

双亲委派模型中,类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码的。

如下为ClassLoader中父加载器的定义:

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;
}

4)双亲委派的实现原理

双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现并不复杂。

实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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 thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    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、先检查类是否已经被加载过

2、若没有加载则调用父加载器的loadClass()方法进行加载

3、若父加载器为空则默认使用启动类加载器作为父加载器。

4、如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

5)为什么需要破坏双亲委派

假设我们有一个接口 PaymentService 和两个实现类:

  • PaymentServiceImplA(来自 ThirdPartyA
  • PaymentServiceImplB(来自 ThirdPartyB

如果这两个实现类都被放在应用的类路径下,并且你使用了双亲委派机制,加载过程如下:

  1. 加载请求:当你在代码中使用 PaymentServiceImplAPaymentServiceImplB 时,类加载器会收到加载请求。
  2. 委派机制
    • 首先,Bootstrap ClassLoader 会尝试加载请求的类。如果该类不在 Java 核心库中(比如 java.langjava.util 等),则 Bootstrap ClassLoader 会失败。
    • 然后,Extension ClassLoader 会尝试加载。如果类仍然不在扩展库中,则加载失败。
    • 接下来,Application ClassLoader(也称为 Web ClassLoader,在 Web 应用中)会尝试加载这个类。
  3. 加载的结果
    • 如果 PaymentServiceImplAPaymentServiceImplB 都存在于类路径下,Application ClassLoader 将会加载它们。但因为它们属于不同的第三方库,它们的类名必须是唯一的。

可能出现的问题

  • 命名冲突:如果 ThirdPartyAThirdPartyB 的实现类都定义为 PaymentServiceImpl,这将导致类命名冲突,最终只有一个实现会被加载,而另一个可能会被忽略或引发错误。
  • 版本冲突:如果 PaymentServiceImplAPaymentServiceImplB 依赖于不同版本的同一库(例如,commons-logging),它们将会加载到同一个 Application ClassLoader 中,这也可能导致运行时错误。

6)怎么破坏双亲委派

知道了双亲委派模型的实现,那么想要破坏双亲委派机制就很简单了。

因为他的双亲委派过程都是在loadClass方法中实现的,那么想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。

loadClass()、findClass()、defineClass()区别

ClassLoader中和类加载有关的方法有很多,前面提到了loadClass,除此之外,还有findClass和defineClass等,那么这几个方法有什么区别呢?

  • loadClass()
    • 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中。
  • findClass()
    • 根据名称或位置加载.class字节码
  • definclass()
    • 把字节码转化为Class

这里面需要展开讲一下loadClass和findClass,我们前面说过,当我们想要自定义一个类加载器的时候,并且像破坏双亲委派原则时,我们会重写loadClass方法。

那么,如果我们想定义一个类加载器,但是不想破坏双亲委派模型的时候呢?

这时候,就可以继承ClassLoader,并且重写findClass方法。findClass()方法是JDK1.2之后的ClassLoader新添加的一个方法。

 /**
 * @since  1.2
 */
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

这个方法只抛出了一个异常,没有默认实现。

JDK1.2之后已不再提倡用户直接覆盖loadClass()方法,而是建议把自己的类加载逻辑实现到findClass()方法中。

因为在loadClass()方法的逻辑里,如果父类加载器加载失败,则会调用自己的findClass()方法来完成加载。

所以,如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承ClassLoader,并且在findClass中实现你自己的加载逻辑即可。

7)破坏双亲委派的例子

1. Tomcat 的类加载机制

背景

Tomcat 是一个广泛使用的 Java Web 服务器和 Servlet 容器。它使用自己的类加载机制来处理 Web 应用和其他组件的类加载。

破坏双亲委派的原因

  • 隔离性:Tomcat 需要将不同的 Web 应用(WAR 文件)隔离开来,以防止它们之间的类冲突。不同应用可能会依赖于相同名称的类,但这些类的实现可能不同。
  • 灵活性:允许不同的 Web 应用使用不同版本的同一个库(例如,commons-logging),而不影响其他应用。

实现

Tomcat 使用多个类加载器:

  • Web 应用类加载器:负责加载应用的类。
  • 父加载器(通常是 Application ClassLoader):负责加载 Tomcat 自身的类和一些共享库。

具体而言,当 Tomcat 加载一个 Web 应用时,它的类加载器会优先加载应用内的类。如果该类在应用内找不到,才会委托给父加载器。这种方式允许 Web 应用使用自己的类而不是全局共享的类。

2. JBoss/WildFly 的类加载机制

背景

JBoss/WildFly 是另一个流行的 Java EE 应用服务器,采用了类似的策略来处理类加载。

破坏双亲委派的原因

  • 模块化:JBoss/WildFly 允许开发者将应用划分为多个模块,每个模块可以有自己独立的依赖。
  • 防止版本冲突:允许不同模块之间使用不同版本的相同库。

实现

  • JBoss/WildFly 使用模块加载器,模块中的类默认不经过双亲委派机制。每个模块都可以有自己的类路径,减少类冲突的风险。
3. 例子总结

例如,如果在 Tomcat 中有两个 Web 应用:

  • 应用 A 使用 commons-logging 的 1.1 版本。
  • 应用 B 使用 commons-logging 的 1.2 版本。

如果不破坏双亲委派机制,两个应用会共享同一个 commons-logging 类,这可能导致运行时错误和版本不兼容。但由于 Tomcat 的类加载器会优先加载应用自身的类,因此各自的 commons-logging 版本会被正确加载。

8)类加载器的使用场景

什么时候使用默认加载器
  1. 常规应用开发
    • 大多数 Java 应用程序、Web 应用和企业应用都可以使用默认的类加载器(Application ClassLoader),因为它能够从类路径中自动加载需要的类。
  2. 使用标准库
    • 当你的应用仅依赖于 Java 标准库和已经在类路径下的第三方库时,默认类加载器通常足够。
什么时候使用自定义类加载器
  1. 特殊路径加载
    • 当需要从非标准路径(如网络、数据库或特定文件夹)加载类时,使用自定义类加载器可以满足这种需求。
  2. 版本冲突管理
    • 在同一项目中需要使用多个版本的同名类时,自定义类加载器可以隔离它们,避免命名冲突。
  3. 动态生成类
    • 如果你的应用需要在运行时动态生成和加载类(例如,通过字节码操作库),则自定义类加载器是必要的。
  4. 类增强和修改
    • 对于需要在类加载时进行字节码修改或增强(如 AOP 框架),使用自定义类加载器是合适的。

参考:https://www.cnblogs.com/hollischuang/p/14260801.html

标签:委派,loadClass,ClassLoader,双亲,findClass,浅析,加载
From: https://www.cnblogs.com/awstan/p/18514371

相关文章

  • 2 类加载子系统(类加载器、双亲委派)
    类加载系统加载类时分为三个步骤,加载、链接、初始化,下面展开介绍。类加载子系统结构图:1类加载器JVM使用类加载器加载class文件,类加载器可分为引导类加载器和自定义类加载器两种。引导类加载器(BootstrapClassLoader),有时也被称作启动类加载器或者零类加载器(NullClassLoad......
  • 【星闪开发连载】WS63E模块的雷达功能浅析
    目录引言功能简介程序分析操作步骤简单测试结语引言WS63E星闪模块有个特色功能就是雷达运动感知,检测物体是否有运动,作用距离不超过6米。hi3863芯片本身不带雷达功能,是模块提供的相关功能。海思还有个WS63星闪模块,没有雷达感知能力。功能简介从开发板的图片上可以......
  • 实战网络攻防中的高版本JDK反射类加载浅析
    就是要打骨折http://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247486065&idx=2&sn=b30ade8200e842743339d428f414475e&chksm=c0e4732df793fa3bf39a6eab17cc0ed0fca5f0e4c979ce64bd112762def9ee7cf0112a7e76af&scene=21#wechat_redirect《Java代码审计》http:......
  • 智能手表核心芯片~手表心率监测芯片AFE4900浅析(附一篇智能手表专利推荐)
    智能手表核心芯片~手表心率监测芯片AFE4900浅析(附一篇智能手表专利推荐)本期是平台君和您分享的第89期内容2024年8月,安徽华米信息技术及美国的智能手表品牌ZEPP公司在美国对深圳思佰特公司提起诉讼,涉及专利、商标和不正当竞争。起诉书(来源:RPX网站)看到这则新闻,平台君马......
  • 浅析RocketMQ
    SpringBoot引入RocketMQ快速构建单机RocketMQhttps://www.haveyb.com/article/3079参考这篇文章,快速构建单机RocketMQ项目引入jar包和配置<dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter&......
  • Vue Router 浅析
    路由原理hash和history众所周知,hash和history在前端面试中是很常考的一道题目。在学习本文内容之前,周一对hash和history的认知可能就在hash的url里面多了个#,而history就不会。然后,我认知里还有一个是只有history才能做前后端分离,而hash跟前后端分离没......
  • 如何在git中删除仓库中的文件(步骤浅析)
    在git中删除仓库中的文件的步骤:1.进入Git项目目录;2.使用gitrm命令删除文件;3.提交更改;4.推送更改(如果有远程仓库)。首先,打开终端或命令提示符,并导航到包含您的Git项目的目录。您可以使用cd命令来进入项目目录。1.进入Git项目目录首先,打开终端或命令提示符,并导航到包......
  • HarmonyOS Stage 模型:进程、线程与配置文件之浅析
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。HarmonyOS的Stage模型为开发者提供......
  • 双亲委派机制以及类加载过程就是这样啊
    类加载过程:简洁来说就是将我们的已经完成编译的class字节码文件通过类加载器到我们JVM的内存运行时数据区成为我们可以在程序中可以使用的class对象,而类加载器就是通过双亲委派机制来实现的,这个也是反射的底层实现的原因具体流程: 加载链接 初始化加载:就是通过类加......
  • 初学者浅析C++类与对象
    C++类与对象classclass基本语法classClassName{public://公有成员TypememberVariable;//数据成员ReturnTypememberFunction();//成员函数声明private://私有成员TypeprivateMemberVariable;//数据成员ReturnTypepriva......