Java类加载器及双亲委托机制

Java类加载器及双亲委托机制

Java程序是如何运行起来的,如何做到“一次编译,到处运行”的,Java虚拟机内部是怎么工作的,它的设计遵循着哪些原则,程序出现异常,除了代码层面,还有哪些地方需要排查……

带着上面的问题,开始探索神秘的Java虚拟机

​ 简单说来,类的加载就是将class文件中的二进制数据读取到内存之中,然后将该字节流所代表的静态存储结构转换为方法区中运行时的数据结构,并在堆内存中生成一个该类的java.lang.Class对象,作为访问方法区数据结构的入口,类加载后在内存中的分配情况如下图所示

在这里插入图片描述

​ 类加载的最终产物就是堆内存中的class对象,对同一个ClassLoader来讲,不管某个类被加载了多少次,对应到堆内存中的class对象始终是一个。虚拟机规范中指出了累的加载是通过一个全限定名(包名+类名)来获取二进制的数据流,但是并没有限定必须通过某种方法去获得,一般是通过加载class二进制文件的形式。

JVM内置三大类加载器

​ JVM中提供了三大内置的类加载器,不同的类加载器负责将不同的类加载到JVM内存之中,他们之间严格遵守双亲委托机制。

BootStrap ClassLoader
Ext ClassLoader
Application ClassLoader
根类加载器

​ 根类加载器又称为Bootstrap加载器,该类加载器是最顶层的加载器,没有父类加载器,由C++编写,主要负责虚拟机核心类库的加载,整个java.lang包都是通过由根加载器加载的,可以通过-Xbootclasspath来指定根加载器的路径(这个应该用不到,知道就行)。根类加载器获取不到他的引用,比如调用String.class.getClassLoader()返回null

扩展类加载器

​ 扩展类加载器的父加载器是根加载器,他主要用于加载JAVA_HOME下的jre\lib\ext子目录里面的类库。扩展类加载器是由纯Java语言实现的,它是java.lang.URLClassLoader的子类,他的完整类名是sun.misc.Launcher.ExtClassLoader

系统类加载器

​ 系统类加载器是一种常见的类加载器,其负责加载classpath下的类库资源,比如开发过程中引入的第三方jar包。系统类加载器的父加载器是扩展类加载器,同时也是自定义类加载器的默认父加载器

双亲委托机制

简述

​ 类加载器里面有一个非常重要的机制–双亲委托机制,或者叫父委托机制。当一个类加载器被要求去加载一个类的时候,他并不会直接加载,而是会委托交给他的父加载器去加载,如果该类已经被加载,则返回加载成功,如果没有被加载,则递交给父类,如果父类加载器加载失败,就会交给子类加载器去加载,千言万语不如一张图

已经加载过不需要再加载
CustomerClassLoader
是否已经加载
AppClassLoader
是否已经加载
ExtClassLoader
是否已经加载
BootStrapClassLoader
是否已经加载
是否可以加载
自己加载
是否可以加载
是否可以加载
是否可以加载
加载失败

​ (我发誓,我一定找个时间学画图)结合图片再解释一番,从用户自定义类加载器出发,先询问父类加载器有没有加载过该类,如果没有向上询问,如果到最顶层,依然没有加载,就尝试自己加载,如果自己无法加载,就将加载任务委托给自己的子加载器去加载,如果不能继续往下委托,如果自己可以加载,就返回加载成功

作用
  • 双亲委托机制的存在,并不会存在同一份class文件被加载了两次的情况,在加载某个类之前会先询问父加载器是否有加载过
  • 保证了核心类并不会篡改,比如已经由BootStrapClassLoader加载了String.class,则其他类加载器都不会加载自己定义的核心类,一定程度上防止了危险代码的注入(没有绝对的安全)
为何要打破双亲委托机制

​ 这里先插入一个概念,叫做热部署,是指在不重启程序的情况下修改或者增加程序的功能(为什么没有删除嘞,想删除功能写个开关或者直接把模块停了不就好了)。

public void method(){
  	// 实例化接口
  	Service service = new ServiceImpl();
  	service.invokeMethodA();
}

​ 我们要实现在下次进入method方法时,new ServiceImpl()生成的是修改后的对象,回到文章开头,堆中保存了类的Class对象,方法区保存了类的数据结构,new关键字的作用和class.newInstance()是一样的,也就是说,下回访问class对象的时候,堆里的对象已经是重新加载过后的了

​ 完成上面的骚操作,需要卸载掉堆内的class对象,然后重新加载,JVM规定了一个Class对象只有在满足下面三个条件的时候才会被GC回收:

  • 该类所有的实例都已经被GC

  • 加载该类的ClassLoader实例被回收

  • 该类的class实例没有在其他地方被引用

    JVM自己的三大加载器就算了,只能通过控制自定义的加载器实现上述功能(具体怎么做先挖坑,后面代码补上)

如何打破双亲委托机制

​ 如果使用系统类加载器加载自己的类,无法将其卸载,因为双亲委托机制的存在,JVM里只能有一份Class存在,即使更新了字节码,也不会加载到JVM里,想要重新加载到JVM,只能实现自己的类加载器,卸载掉自己的类加载器,然后用类加载器重新加载字节码

​ 但是双亲委托机制的存在,自己定义的字节码在项目的classpath中,自己的类会由系统类加载器加载,即使卸载掉原有的自定义类加载器,也会因为系统类加载器已经加载了这份原有的字节码而不会去加载新的字节码,因此必须打破这个双亲委托机制

​ 基本实现思路有两个:1.设置自定义加载器的父加载器为拓展类加载器,这样在加载新的字节码的时候,直接委托给拓展类加载器,会跳过系统类加载器,最终这份字节码会被自定义类加载器重新加载,或者将自定义类加载器的父加载器指定为null,加载时会尝试用根加载器加载该类,和刚才的原理相同,都是打破双亲委托机制的办法;2.将自定义类加载器的加载路径指向classpath外,并将该类的字节码至于classpath之外,这样操作的话,该类并不会由系统类加载器加载,只会由自定义类加载器加载,并没有打破双亲委托机制

​ 未完待续—

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值