Java虚拟机-双亲委派机制

双亲委派机制概述

  • Java虚拟机对class文件采用按需加载的方式,也就是说当需要使用该类时,才会将它的class文件加载到内存生成class对象。而且加载某个类的文件是,采用的是双亲委派机制,即把请求交由父类处理,它是一种任务委派模式。
  • 先看下面的一个例子
    新建一个 java.lang 包,在下面自定义一个String类 ,如图
    在这里插入图片描述
    然后在Test中创建String类
public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        String string = new String();
        java.lang.String s2 = new java.lang.String();
        System.out.println("hello");
    }
}

如果创建的对象是我们自定义的类则会输出静态代码块中的 ““我是自定义 String …””,否则不会。
这里结果指定是不会打印的,这就是双亲委派机制的作用。

双亲委派机制原理

  • 如果一个类加载器接收到了类加载请求,它并不会自己先去加载,而是把这个类加载委托给父类加载器去执行。
  • 如果父类加载器还存在父加载器,则进一步向上委托,依次递归,请求将最终达到顶层的启动类加载器。
  • 如果父类可以完成类加载任务,就成功返回,若父类无法完成类加载任务,子加载器才会尝试去加载,这就是双亲委派模式。
    在这里插入图片描述
  • 如果将上面的代码 main方法放在自定义的 String 类中
public class String {

    static {

        System.out.println("我是自定义 String ...");

    }

    public static void main(String[] args) {
        System.out.println("main....");
    }
}

执行的时候报错如下:

在这里插入图片描述
意思就是加载的 java.lang.String 类中没有 main方法,也证明了之前的结论。

双亲委派机制的优势

  • 避免类重复加载
  • 保护程序安全,避免核心API被随意篡改。(如上面的自定义 java.lang.String 类)也包括阻止我们使用和核心api相同的包名来自定义类。

如下图
在这里插入图片描述
报错如下:

Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at Test.main(Test.java:5)

沙箱安全机制

  • 自定义String类,但是在加载自定义String类时会率先使用引导类加载器加载,而引导类加载器在加载过程中会优先加载jdk自带的文件,上面例子报错信息说没有 main 方法,就是因为加载的是 jdk的 java.lang包下的String类。这样可以保证java源码的保护,这就是沙箱安全机制。

破坏双亲委派机制

  • 双亲委派机制模型并不是一个具有强制性约束的模型,而是 Java 设计者推荐给开发者们的类加载实现方式。在 Java 的世界中,大部分类加载都遵循了这个模型,但也有例外的情况。直到Java模块化出现,双亲委派机制已经有三次较大规模“被破坏”的情况。
  • 双亲委派模型第一次被破坏:

双亲委派模型第一次被破坏发生在双亲委派模型出现之前,即jdk1.2 之前。由于双亲委派模型在jdk1.2以后才被引入,但是类加载的概念和抽象类 ClassLoader 已经存在,面对已经存在的用户自定义类加载器代码,Java设计者们引入双亲委派模型不得不作出一些妥协,为了兼容这些已有代码,无法再以技术手段避免 loadClass() 被自类覆盖的可能,只能在 jdk1.2以后的java.lang.ClassLoader 中添加一个新的 protected 方法 findClass() ,并引导用户编写的类加载器逻辑尽可能去重写这个方法,而不是在 loadClass() 中编写代码。loadClass() 方法中,双亲委派的具体逻辑就写在这里面,按照loadClass的逻辑,如果父类加载失败,会自动调用自己的 findClass() 方法来完成加载。这样既不影响用户按照自己的意愿去加载类,也不会破坏双亲委派规则。

  • 双亲委派模型第二次破坏

双亲委派模型的第二次破坏是由这个模型自身的缺陷导致的,双亲委派机制很好的解决了各个类加载器协作时基础类型一致的问题(越基础的类由越上层的类加载器加载),基础类型之所以被称为基础,是因为它们总是作为用户代码继承、调用的API存在,但是程序设计往往没有完美的存在,如果有基础类型需要调用用户的代码,那该怎么办呢?
一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器完成,肯定属于Java中很基础的类型了。但JNDI存在的目的是对资源进行查找和集中管理,它需要调用由其他厂商实现并部署的应用程序的 ClassPath 下的JNDI服务提供者接口的代码,启动类加载器是绝对不会认识这些类的,那该怎么办呢?

为了解决这个困境,Java设计团队引入了不太优雅的设计:线程上下文加载器。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父类中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

有了线程上下文类加载器,程序就可以做一些“舞弊”的事情了。JNDI服务使用这个线程上下文类加载器去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类加载的行为,这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则。不过当SPI的服务提供者多于一个的时候,代码就只能根据具体提供者的类型来硬编码来判断,为了消除这种及其不优雅的实现方式,在JDK6时,如果不是提供了java.util.ServiceLoader,以META-INF/Services中的配置信息,辅以责任链模式,才算是给SPI的加载提供了一种相对合理的解决方案。

  • 双亲委派模型的第三次破坏

双亲委派模型的第三次破坏时用户追求程序动态性而导致的(如:代码热替换,模块热部署,等),就是希望Java程序能像我们电脑外设那样,接上鼠标键盘,不用重启电脑就可以使用。

OSGI 实现模块化部署的关键是它自定义的类加载机制的实现,每一个应用程序模块都有自己的一个类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGI环境下,类加载器不在是双亲委派模型推荐的树状模型,而是进一步发展为更复杂的网状结构,当收到类加载请求时,OSGI将按照以下顺序进行类搜索:

  • 将以Java开头的类,委托给父类加载器
  • 否则,将委派列表名单内的类,委派给父类加载器加载
  • 否则,将Import列表中的类,委派给Export这个类的Bundle的类加载器加载。
  • 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
  • 否则,查找类是否在自己的Fragment Bundle中,如果在则委派给 Fragment bundle的类加载器加载。
  • 否则,查找Dynamic Import 列表的Bundle,委派给对应Bundle的类加载器加载。
  • 否则,类查找失败。

上面查找顺序中,只有前两条符合按照双亲委派模型原则,其余查找类都是在平级的类加载器中进行的。

  • 双亲委派模型的破坏并不一定都是贬义的,只要有明确的目的和充分的理由,突破原有规则无疑也是一种创新。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
JVM中的双亲委派机制是一种类加载机制,它规定了在Java中一个类被加载时如何进行类加载器的选择。根据这个机制,当一个类需要被加载时,首先会由类加载器ClassLoader检查是否已经加载过该类,如果是,则直接返回已经加载过的类;如果不是,则将该请求委派给父类加载器去加载。这样的过程会一直向上委派,直到达到顶层的引导类加载器(Bootstrap ClassLoader)。引用 引用中提到,并不是所有的类加载器都采用双亲委派机制Java虚拟机规范并没有强制要求使用双亲委派机制,只是建议使用。实际上,一些类加载器可能会采用不同的加载顺序,例如Tomcat服务器类加载器就是采用代理模式,首先尝试自己去加载某个类,如果找不到再代理给父类加载器。 引用中提到,引导类加载器(Bootstrap ClassLoader)是最早开始工作的类加载器,负责加载JVM的核心类库,例如java.lang.*包中的类。这些类在JVM启动时就已经被加载到内存中。 综上所述,JVM双亲委派机制是一种类加载机制,它通过类加载器的委派方式来加载类,首先检查是否已经加载过该类,如果没有则委派给父类加载器去加载,直到达到顶层的引导类加载器。不过,并不是所有的类加载器都采用该机制,一些类加载器可能会采用不同的加载顺序。引用<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [JVM-双亲委派机制](https://blog.csdn.net/m0_51608444/article/details/125835862)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [jvm-双亲委派机制](https://blog.csdn.net/y08144013/article/details/130724858)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值