📝个人主页:五敷有你      

 🔥系列专栏: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内置的服务提供发现机制。

工作原理
  1. 在ClassPath路径下的META-INF/services文件夹中(路径是固定的SPI机制会扫描这个文件夹),以接口的全限定名来命名文件名,对应的文件里面写接口的实现
  2. 在使用ServiceLoader加载实现类

总结
  • 启动类加载器加载DriverManager
  • 在初始化DriverManager时,通过SPI机制加载Jar包中的mysql驱动
  • SPI中利用线程上下文类加载器(应用程序类加载器) 去加载并创建对象。

OSGi模块化

历史上,OSGi模块化框架,它存在同级之间的类加载器的委托加载,OSGi还使用类加载器实现了热部署功能

热部署指的是在服务器不停止的情况下,动态的更新字节码文件到内存中

案例:

使用arthas不停机解决线上问题

背景:小李的团队将代码上线后,发现bug,但用户着急使用,如果重新打包在发布需要一个多小时的时间,所以希望使用arthas尽快解决问题

思路:

  1. 在出问题的服务上部署一个arthas并启动。
  2. jad(反编译) --source-only 类全限定名 > 目录/文件名.java
    1. jad 命令 反编译,然后可以用其他编辑器修改源码(比如Vim)
  3. mc -c 类加载器的hashcode 目录/文件名.java -d 输出目录
    1. mc 命令用来编译修改过的代码。
  4. retransform class 文件所在目录/xxx.class
    1. retransform命令加载新的字节码

注意:

  1. 只是暂时更新到内存中,程序重启后,字节码文件会恢复,除非将class文件放入jar包更新,
  2. 使用retransform不能添加方法或字段,也不能更新正在执行的方法

JDK9之后的类加载器

JKD8及之前的版本,扩展类加载器和应用程序类加载器的源码位于rt.jar包中的sun.misc.Launcher.java中

JDK9引入了module的概念,类加载器在设计上发生很大变化

  1. 启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中。
    1. Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件。
    2. 启动类加载器依然无法通过java代码获取到,返回的仍然是null,保持了统一

  1. 扩展类加载器被替换成了平台类加载器(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实现了一整套类加载机制,允许同级类加载器之间互相调用。

更多推荐