说一说你对双亲委派模型的理解
三层类加载器、双亲委派流程
标准回答
双亲委派模型依赖于三层类加载器:
<JAVA_HOME>\lib
<JAVA_HOME>\lib\ext
双亲委派模型的工作过程是,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层都是如此。因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
使用双亲委派模型的好处是,Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类。
加分回答
双亲委派模型主要有3种“被破坏”的情况:
双亲委派模型的第一次“被破坏”发生在双亲委派模型出现之前
双亲委派模型在JDK 1.2之后才被引入,但是类加载器的概念和抽象类ClassLoader则在Java的第一个版本中就已经存在,面对已经存在的用户自定义类加载器的代码,Java设计者们引入双亲委派模型时不得不做出一些妥协。为了兼容这些已有代码,只能在之后的ClassLoader中添加一个protected方法findClass(),并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在loadClass()中编写代码。
双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷导致的
一个典型的例子便是JNDI服务,JNDI存在的目的就是对资源进行查找和集中管理,它需要调用由其他厂商实现并部署在应用程序的ClassPath下的JNDI服务提供者接口的代码,现在问题来了,启动类加载器是绝不可能认识、加载这些代码的,那该怎么办?
为了解决这个困境,Java的设计团队只好引入了一个不太优雅的设计:线程上下文类加载器。这个类加载器可以通过Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
有了线程上下文类加载器,JNDI服务就可以使用它去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为,这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则,但也是无可奈何的事情。
双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的
这里所说的动态性指的是一些非常热门的名词:代码热替换、模块热部署等。说白了就是希望Java应用程序能像我们的电脑外设那样,接上鼠标、U盘,不用重启机器就能立即使用。
在这个领域,IBM公司主导的OSGi 提案是业界事实上的Java模块化标准。OSGi实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器不再双亲委派模型推荐的树状结构,而是进一步发展为更加复杂的网状结构。
双亲委派模型的类加载器示意图:
双亲委派模型实现的核心源码:
// ClassLoader 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); ... } } if (resolve) { resolveClass(c); } return c; } }
上图是类加载器的层次关系,称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。
这道题你会答吗?花几分钟告诉大家答案吧!
扫描二维码,关注牛客网
下载牛客APP,随时随地刷题
得分点
三层类加载器、双亲委派流程
参考答案
标准回答
双亲委派模型依赖于三层类加载器:
<JAVA_HOME>\lib
目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的类库加载到虚拟机的内存中。<JAVA_HOME>\lib\ext
目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。 双亲委派模型的工作过程是,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层都是如此。因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
使用双亲委派模型的好处是,Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类。
加分回答
双亲委派模型主要有3种“被破坏”的情况:
双亲委派模型的第一次“被破坏”发生在双亲委派模型出现之前
双亲委派模型在JDK 1.2之后才被引入,但是类加载器的概念和抽象类ClassLoader则在Java的第一个版本中就已经存在,面对已经存在的用户自定义类加载器的代码,Java设计者们引入双亲委派模型时不得不做出一些妥协。为了兼容这些已有代码,只能在之后的ClassLoader中添加一个protected方法findClass(),并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在loadClass()中编写代码。
双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷导致的
一个典型的例子便是JNDI服务,JNDI存在的目的就是对资源进行查找和集中管理,它需要调用由其他厂商实现并部署在应用程序的ClassPath下的JNDI服务提供者接口的代码,现在问题来了,启动类加载器是绝不可能认识、加载这些代码的,那该怎么办?
为了解决这个困境,Java的设计团队只好引入了一个不太优雅的设计:线程上下文类加载器。这个类加载器可以通过Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
有了线程上下文类加载器,JNDI服务就可以使用它去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为,这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则,但也是无可奈何的事情。
双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的
这里所说的动态性指的是一些非常热门的名词:代码热替换、模块热部署等。说白了就是希望Java应用程序能像我们的电脑外设那样,接上鼠标、U盘,不用重启机器就能立即使用。
在这个领域,IBM公司主导的OSGi 提案是业界事实上的Java模块化标准。OSGi实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器不再双亲委派模型推荐的树状结构,而是进一步发展为更加复杂的网状结构。
延伸阅读
双亲委派模型的类加载器示意图:
双亲委派模型实现的核心源码: