我的疑问
ClassLoader 与委派模型
ClassLoader 体系
加载委派分析
提问解答
热加载的技术原理
ClassLoader 如何实现动态加载 jar, 实现插件模式系统?
补充题目
最近在读许令波的
深入分析 Java web 技术内幕
一书, 对于学习 Java 以来一直有的几个疑惑得到了解答, 遂记录下来.
我的疑问 #
双亲委派模型 (实际上是一个翻译错误, 英文为 parent delegation, 只是一个父委托模型) 是什么? 如何实现? 为什么这样实现?
热加载的技术原理是什么?
ClassLoader 如何实现动态加载 jar, 实现插件模式系统?
下面跟着教程来寻找这些答案.
ClassLoader 与委派模型 #
ClassLoader 体系 #
ClassLoader 顾名思义是类加载器(准确来说为 JVM 平台类加载器抽象父类), 主要功能负责将 Class 加载到 JVM 中, 其所使用的加载策略叫做双亲委派模型. 其主要有如下方法
defineClass 负责把 class 文件而字节流解析为 JVM 能够识别的 Class 对象, 这意味着只要能拿到对应的 Class 字节流就可以完成对象实例化, 注意该 Class 对象在使用前必须 resolve
findClass 自定义规则时复写的方法, 通常与 defineClass 一起使用, 找到一个 class 文件, 然后 defineClass 解析后生成 Class 对象,
loadClass JVM 所调用的加载方法, 该方法会在 findLoadedClass,loadClass(String)都没找到时调用 findClass 寻找 Class 对象, 然后根据 resolve 的 flag 来决定是否链接.
resolveClass 链接一个 Class 对象, 在这个操作之后才可以使用该 Class 对象
JVM 平台提供三个 ClassLoader:
Bootstrap ClassLoader, 由 C++ 实现的 ClassLoader, 不属于 JVM 平台, 由 JVM 自己控制, 主要加载 JVM 自己工作所需要的类, 当类加载器的 parent 为 null 时会使用 Bootstrap ClassLoader 去加载, 其也不再双亲委派模型中承担角色.
ExtClassLoader,JVM 在 sun.misc.Launcher 中主动实例化的类加载器, 主要加载 System.getProperty(java.ext.dirs)对应的目录下的文件(具体源码中可以看到), 同时也是 AppClassLoader 的父类
AppClassLoader, 由 ExtClassLoader 为 parent 创建出来的, 同样为 sun.misc.Launcher 的内部类, 主要加载 System.getProperty(java.class.path)下的类, 这个目录就是我们经常用到的 classpath, 包括当前应用以及 jre 相关 jar 包路径.
那么不算
Bootstrap ClassLoader
,JVM 体系的 ClassLoader 结构如下
ClassLoader 作为抽象父类其实是一装饰者模式中的 Decorator 角色, AppClassLoader 本质上是对 ExtClassLoader 的增强处理, 再看初始化方式可以简化为
1
| Launcher.AppClassLoader.getAppClassLoader(Launcher.ExtClassLoader.getExtClassLoader()) |
是不是和 IO 套用初始化很像? 其实双亲委派按照我的理解本质上就是装饰者模式的应用, 使用组合代替了继承只不过这个被装饰者叫做 parent, 思想上一致只是用法的不同.
另外这张图也说明如果想要实现自己的 ClassLoader, 只需要继承
java.net.URLClassLoader
, 然后自定义加载逻辑即可.
加载委派分析 #
接下来重点看 loadClass()方法, 该方法为加载 class 二进制文件的核心方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 当父加载器不存在的时候会尝试使用 BootStrapClassLoader 作为父类 < br ow="0" oh="0"> if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } //c 为 null 则证明父加载器没有加载到, 进而使用子类本身的加载策略 `findClass()` 方法 < br ow="0" oh="0"> if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); |
当父类不存在时调用
java.lang.ClassLoader#findBootstrapClass
使用 BootStrapClass 加载, 该方法是一个 native 方法, 也进一步说明了
Bootstrap Classloader
与 JVM 体系的 ClassLoader 没什么父子类关系. 完全独立.
提问解答 #
那么开始回答问题
1. 双亲委派模型是什么?
上述加载流程是
使用 parent 加载器加载类
->
parent 不存在使用 BootStrapClassLoader 加载
->
加载不到则使用子类的加载策略
, 这里要注意
BootStrapClassLoader
是由 C++ 实现的 JVM 内部的加载工具, 其没有对应的 Java 对象, 因此不在这个委派体系中, 类加载器本质上是装饰者模式组合思想的应用.
那么双亲是什么? 看 ClassLoader 的注释就能发现这只是个翻译问题 parent->双亲, 明明是单亲委派, 装饰者模式是单类增强委托.
RednaxelaFX 关于这点的证实
2. 委派模型如何实现?
来源: https://juejin.im/entry/5aaf71e86fb9a028de448205