📝个人主页:五敷有你
🔥系列专栏:JVM
⛺️稳中求进,晒太阳
打破双亲委派机制
打破双亲委派机制三种方法
自定义类加载器
ClassLoader包含了四个核心方法
//由类加载器子类实现,获取二进制数据调用defineClass,比如URLClassLoader会根据文件路径去获取类文件中的二进制文件
(不想打破就重写它)
public Class<?> findClass(String name)
//类加载的入口,提供了双亲委派机制,内部会调用findClass(打破双亲委派,就重写它)
protected Class<?> loadClass(String name)
//做一些类名的校验,然后调用虚拟机底层方法将字节码信息加载到虚拟机内存中
protected final Class<?> defineClass(String name,byte[] b,int off,int len)
//执行类生命周期的连接阶段
protected final void resolveClass(Class<?> c)
双亲委派机制核心代码阅读
阅读双亲委派机制的核心代码,分析如何通过自定义类加载器打破双亲委派机制。
打破双亲委派机制的核心就是讲下面的代码重写
思考
问题一:
自定义类加载器的父类怎么是AppClassLoader呢?
以Jdk8为例,ClassLoader类中提供了构造方法设置parent的内容:
这个构造方法由另外一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的是AppClassLoader
问题2
两个自定义类加载器加载相同限定名的类,不会冲突吗?
不会冲突,在同一个Java虚拟机中,只有相同类加载器+相同的类限定名才会被认为是同一个类。
在Arthas中使用sc –d 类名的方式查看具体的情况
线程上下文类加载器(JDBC案例)
JDBC中使用了DriverManager来管理项目中的不同数据库的驱动,比如mysql驱动,oracle驱动
DrvierManager类位于rt.jar包中,由启动类加载器加载
依赖中的mysql驱动对应的类,由应用程序类加载器来加载。
DriverManager属于rt.jar是启动类加载器的,而用户jar包中的驱动需要由应用程序类加载器加载,这就违反了双亲委派机制。
思考
DriverManager怎么知道jar包中要加载的驱动在哪里?
DriverManager使用SPI机制,最终加载jar包中的对应驱动类
SPI机制
SPI全称(Service Provider Interface)是JDK内置的服务提供发现机制。
工作原理
- 在ClassPath路径下的META-INF/services文件夹中(路径是固定的SPI机制会扫描这个文件夹),以接口的全限定名来命名文件名,对应的文件里面写接口的实现
- 在使用ServiceLoader加载实现类
总结
- 启动类加载器加载DriverManager
- 在初始化DriverManager时,通过SPI机制加载Jar包中的mysql驱动
- SPI中利用线程上下文类加载器(应用程序类加载器) 去加载并创建对象。
OSGi模块化
历史上,OSGi模块化框架,它存在同级之间的类加载器的委托加载,OSGi还使用类加载器实现了热部署功能
热部署指的是在服务器不停止的情况下,动态的更新字节码文件到内存中
案例:
使用arthas不停机解决线上问题
背景:小李的团队将代码上线后,发现bug,但用户着急使用,如果重新打包在发布需要一个多小时的时间,所以希望使用arthas尽快解决问题
思路:
- 在出问题的服务上部署一个arthas并启动。
- jad(反编译) --source-only 类全限定名 > 目录/文件名.java
- jad 命令 反编译,然后可以用其他编辑器修改源码(比如Vim)
- mc -c 类加载器的hashcode 目录/文件名.java -d 输出目录
- mc 命令用来编译修改过的代码。
- retransform class 文件所在目录/xxx.class
- retransform命令加载新的字节码
注意:
- 只是暂时更新到内存中,程序重启后,字节码文件会恢复,除非将class文件放入jar包更新,
- 使用retransform不能添加方法或字段,也不能更新正在执行的方法
JDK9之后的类加载器
JKD8及之前的版本,扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java中
JDK9引入了module的概念,类加载器在设计上发生很大变化
- 启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中。
- Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件。
- 启动类加载器依然无法通过java代码获取到,返回的仍然是null,保持了统一
- 扩展类加载器被替换成了平台类加载器(Platform Class Loader)。
平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是为了与老版本的设计方案兼容,自身没有特殊的逻辑。
总结
1、类加载器的作用是什么?
类加载器(ClassLoader)负责在类加载过程中的字节码获取并加载到内存这一部分。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层方法将byte[]转换成方法区和堆中的数据
2、有几种常见的类加载器?
1.启动类加载器(Bootstrap ClassLoader)加载核心类
2.扩展类加载器(Extension ClassLoader)加载扩展类
3.应用程序类加载器(Application ClassLoader)加载应用classpath中的类
4.自定义类加载器,重写findClass方法。JDK9及之后扩展类加载器(Extension ClassLoader)变成了平台类加载器(PlatformClassLoader)
3、什么是双亲委派机制?
每个Java实现的类加载器中保存了一个成员变量叫“父”(Parent)类加载器。自底向上查找是否加载过,再由顶向下进行加载。避免了核心类被应用程序重写并覆盖的问题,提升了安全性。
4、怎么打破双亲委派机制?
1、重写loadClass方法,不再实现双亲委派机制。
2、JNDI、JDBC、JCE、JAXB和JBI等框架使用了SPI机制+线程上下文类加载器。
3、OSGi实现了一整套类加载机制,允许同级类加载器之间互相调用。