“虐人的”双亲委派机制

这篇具有很好参考价值的文章主要介绍了“虐人的”双亲委派机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

这些问题,看看你能回答上来多少个:

1、什么是双亲委派? 2、为什么需要双亲委派,不委派有什么问题? 3、”父加载器”和”子加载器”之间的关系是继承的吗? 4、双亲委派是怎么实现的? 5、我能不能主动破坏这种双亲委派机制?怎么破坏? 6、为什么重写loadClass方法可以破坏双亲委派,这个方法和findClass()、defineClass()区别是什么? 7、说一说你知道的双亲委派被破坏的例子吧 8、为什么JNDI、JDBC等需要破坏双亲委派? 9、为什么TOMCAT要破坏双亲委派? 10、谈谈你对模块化技术的理解吧!

以上,10个问题,从头开始答,你大概可以坚持到第几题?

什么是双亲委派机制

首先,我们知道,虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢?

这就不得不提到”双亲委派机制”。

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

  • Bootstrap ClassLoader 启动类加载器
  • Extention ClassLoader 标准扩展类加载器
  • Application ClassLoader 应用类加载器
  • User ClassLoader 用户自定义类加载器

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

“虐人的”双亲委派机制

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

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

那么,什么情况下父加载器会无法加载某一个类呢?

其实,Java中提供的这四种类型的加载器,是有各自的职责的:

  • 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加载器加载的。

为什么需要双亲委派?

如上面我们提到的,因为类加载器之间有严格的层次关系,那么也就使得Java类也随之具备了层次关系。

或者说这种层次关系是优先级。

比如一个定义在java.lang包下的类,因为它被存放在rt.jar之中,所以在被加载过程汇总,会被一直委托到Bootstrap ClassLoader,最终由Bootstrap ClassLoader所加载。

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

这种机制有几个好处。

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

另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。

那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。

“父子加载器”之间的关系是继承吗?

很多人看到父加载器、子加载器这样的名字,就会认为Java中的类加载器之间存在着继承关系。

甚至网上很多文章也会有类似的错误观点。

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

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

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

双亲委派是怎么实现的?

双亲委派模型对于保证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()方法进行加载。

如何主动破坏双亲委派机制?

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

因为他的双亲委派过程都是在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中实现你自己的加载逻辑即可。

双亲委派被破坏的例子

双亲委派机制的破坏不是什么稀奇的事情,很多框架、容器等都会破坏这种机制来实现某些功能。

第一种被破坏的情况是在双亲委派出现之前。

由于双亲委派模型是在JDK1.2之后才被引入的,而在这之前已经有用户自定义类加载器在用了。所以,这些是没有遵守双亲委派原则的。

第二种,是JNDI、JDBC等需要加载SPI接口实现类的情况。

第三种是为了实现热插拔热部署工具。为了让代码动态生效而无需重启,实现方式时把模块连同类加载器一起换掉就实现了代码的热替换。

第四种时tomcat等web容器的出现。

第五种时OSGI、Jigsaw等模块化技术的应用。

为什么JNDI,JDBC等需要破坏双亲委派?

我们日常开发中,大多数时候会通过API的方式调用Java提供的那些基础类,这些基础类时被Bootstrap加载的。

但是,调用方式除了API之外,还有一种SPI的方式。

如典型的JDBC服务,我们通常通过以下方式创建数据库连接:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");

在以上代码执行之前,DriverManager会先被类加载器加载,因为java.sql.DriverManager类是位于rt.jar下面的 ,所以他会被根加载器加载。

类加载时,会执行该类的静态方法。其中有一段关键的代码是:

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

这段代码,会尝试加载classpath下面的所有实现了Driver接口的实现类。

那么,问题就来了。

DriverManager是被根加载器加载的,那么在加载时遇到以上代码,会尝试加载所有Driver的实现类,但是这些实现类基本都是第三方提供的,根据双亲委派原则,第三方的类不能被根加载器加载。

那么,怎么解决这个问题呢?

于是,就在JDBC中通过引入ThreadContextClassLoader(线程上下文加载器,默认情况下是AppClassLoader)的方式破坏了双亲委派原则。

我们深入到ServiceLoader.load方法就可以看到:

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

第一行,获取当前线程的线程上下⽂类加载器 AppClassLoader,⽤于加载 classpath 中的具体实现类。

为什么Tomcat要破坏双亲委派

我们知道,Tomcat是web容器,那么一个web容器可能需要部署多个应用程序。

不同的应用程序可能会依赖同一个第三方类库的不同版本,但是不同版本的类库中某一个类的全路径名可能是一样的。

如多个应用都要依赖hollis.jar,但是A应用需要依赖1.0.0版本,但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.hollis.Test.class。

如果采用默认的双亲委派类加载机制,那么是无法加载多个相同的类。

所以,Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。

Tomcat的类加载机制:为了实现隔离性,优先加载 Web 应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。

模块化技术与类加载机制

近几年模块化技术已经很成熟了,在JDK 9中已经应用了模块化的技术。

其实早在JDK 9之前,OSGI这种框架已经是模块化的了,而OSGI之所以能够实现模块热插拔和模块内部可见性的精准控制都归结于其特殊的类加载机制,加载器之间的关系不再是双亲委派模型的树状结构,而是发展成复杂的网状结构。

“虐人的”双亲委派机制

在JDK中,双亲委派也不是绝对的了。

在JDK9之前,JVM的基础类以前都是在rt.jar这个包里,这个包也是JRE运行的基石。

这不仅是违反了单一职责原则,同样程序在编译的时候会将很多无用的类也一并打包,造成臃肿。

在JDK9中,整个JDK都基于模块化进行构建,以前的rt.jar, tool.jar被拆分成数十个模块,编译的时候只编译实际用到的模块,同时各个类加载器各司其职,只加载自己负责的模块。

Class<?> c = findLoadedClass(cn);
if (c == null) {
    // 找到当前类属于哪个模块
    LoadedModule loadedModule = findLoadedModule(cn);
    if (loadedModule != null) {
        //获取当前模块的类加载器
        BuiltinClassLoader loader = loadedModule.loader();
        //进行类加载
        c = findClassInModuleOrNull(loadedModule, cn);
     } else {
          // 找不到模块信息才会进行双亲委派
            if (parent != null) {
              c = parent.loadClassOrNull(cn);
            }
      }
}

总结

以上,从什么是双亲委派,到如何实现与破坏双亲委派,又从破坏双亲委派的示例等多个方面全面介绍了关于双亲委派的知识。

相信通过学习本文,你一定对双亲委派机制有了更加深刻的了解。

阅读过本文之后,反手在简历上写下:熟悉Java的类加载机制,不服来问!文章来源地址https://www.toymoban.com/news/detail-442380.html

到了这里,关于“虐人的”双亲委派机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JVM类加载和双亲委派机制

    当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把类加载到JVM,本文主要说明类加载机制和其具体实现双亲委派模式。 类加载过程 : 类加载的过程是将类的字节码加载到内存中的过程,主要包括:加载--链接--初始化,其中链接还包括验证、准备、

    2024年02月09日
    浏览(43)
  • JVM:双亲委派机制&类加载器

    Java运行时环境有一个 java.lang 包,里面有一个 ClassLoader 类 我们自定义一个 String 类在 java.lang 包下,下面的main方法报错。原因是: 根据双亲委派机制,会向上找先是找到了 应用程序加载器(appClassLoader) ,然后向上找 扩展类加载器(ExtClassLoader) ,最后找 根类加载器(Boot Strap

    2024年02月01日
    浏览(36)
  • Tomcat是如何打破“双亲委派“机制的

    目录 一、什么是双亲委派机制? 二、什么情况下我们需要破坏双亲委派模型?

    2024年02月22日
    浏览(47)
  • 【Java】深入了解双亲委派机制(常说的类加载机制)

    ava虚拟机(JVM)的类加载机制是Java应用中不可或缺的一部分。本文将详细介绍JVM的双亲委派机制,并阐述各关键点。 双亲委派机制(Parent-Delegate Model)是Java类加载器中采用的一种类加载策略。该机制的核心思想是:如果一个类加载器收到了类加载请求,默认先将该请求委托

    2024年02月04日
    浏览(35)
  • 【JAVA基础】JVM之类加载--双亲委派机制

    1. 类加载的过程 描述: 我们写的 .java 文件通过编译成字节码文件 .class 文件,然后再通过我们的类加载器:Class Loader,反射以后,类模板存在方法区,把实例化的对象存在堆里; 看图: 对象的hashcode值 解释: 从图中我们可以看出,从同一个类模板new出来三个对象(实例化

    2024年01月23日
    浏览(43)
  • JVM(类的加载与ClassLoader、双亲委派机制)

    类在内存中完整的生命周期: 加载--使用--卸载 。其中加载过程又分为: 装载、链接、初始化 三个阶段。 当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、链接、初始化三个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成这三个步骤,

    2024年02月03日
    浏览(43)
  • 解析Java类加载的运行机制和双亲委派模型

    类加载形如把.class文件,加载到内存中,得到类对象这样的过程 类加载一共分为五个步骤: 加载 验证 准备 解析 初始化 加载 类加载的第一步是将编译好的 Java 类的字节码文件加载到 Java 虚拟机(JVM)中。类加载器会根据类的名称找到对应的字节码文件,并将其读取到内存

    2024年02月15日
    浏览(39)
  • 从类加载到双亲委派:深入解析类加载机制与 ClassLoader

    目录 在 Java 编程中,类加载是一个关键的技术点,它负责将类引入 Java 虚拟机(JVM)使得程序能够正确地加载、链接、初始化类;类加载的过程是 Java 程序执行的基础,它涉及从磁盘或网络上加载类的字节码,解析类的符号引用,最终将类加载到内存中供程序使用 类加载的

    2024年02月11日
    浏览(38)
  • 【JVM从入门到实战】(六)类加载器的双亲委派机制

    在Java中如何使用代码的方式去主动加载一个类呢? 方式1:使用Class.forName方法,使用当前类的类加载器去加载指定的类。 方式2:获取到类加载器,通过类加载器的loadClass方法指定某个类加载器加载。 双亲委派机制: 自底向上查找是否加载过,再由顶向下进行加载 面试题:

    2024年02月04日
    浏览(47)
  • 深入理解Java类加载机制中的双亲委派模型--根据源码探讨

    前言: 今天和大家探讨一道Java中经典的面试题,这道面试题经常出现在各个公司的面试中,本篇文章主要讲解 ava类加载机制中的双亲委派模型 的知识。该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读。 如果文章有什么需要改进的地方欢迎大佬提出,对大佬有帮助

    2024年02月14日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包