Java 中的类何时被加载器加载

1.调用类构造器
2.调用类中的静态(static)变量或者静态方法

Java 中 ClassLoader

JVM 中自带 3 个类加载器:
1.启动类加载器 BootstrapClassLoader
2.扩展类加载器 ExtClassLoader (JDK 1.9 之后,改名为 PlatformClassLoader)
3.系统加载器 APPClassLoader

APPClassLoader 系统类加载器

image.png
可以看出,AppClassLoader 主要加载系统属性“java.class.path”配置下类文件,也就是环境变量 CLASS_PATH 配置的路径。因此 AppClassLoader 是面向用户的类加载器,我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

ExtClassLoader 扩展类加载器

image.png
可以看出,ExtClassLoader 加载系统属性“java.ext.dirs”配置下类文件,可以打印出这个属性来查看具体有哪些文件:
image.png

BootstrapClassLoader 启动类加载器

由 C/C++ 语言编写的,它本身属于虚拟机的一部分。因此我们无法在 Java 代码中直接获取它的引用。如果尝试在 Java 层获取 BootstrapClassLoader 的引用,系统会返回 null。
BootstrapClassLoader 加载系统属性“sun.boot.class.path”配置下类文件,可以打印出这个属性来查看具体有哪些文件:
image.png

双亲委派模式

当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程.
具体实现代码是在 ClassLoader.java 中的 loadClass 方法中,如下所示:
image.png
1)判断该 Class 是否已加载,如果已加载,则直接将该 Class 返回。
2)如果该 Class 没有被加载过,则判断 parent 是否为空,如果不为空则将加载的任务委托给parent。
3)如果 parent == null,则直接调用 BootstrapClassLoader 加载该类。
4)如果 parent 或者 BootstrapClassLoader 都没有加载成功,则调用当前 ClassLoader 的 findClass 方法继续尝试加载。

在每一个 ClassLoader 中都有一个 CLassLoader 类型的 parent 引用,并且在构造器中传入值。如果我们继续查看源码,可以看到 AppClassLoader 传入的 parent 就是 ExtClassLoader,而 ExtClassLoader 并没有传入任何 parent,也就是 null。
PS:“双亲委派”机制只是 Java 推荐的机制,并不是强制的机制。我们可以继承 java.lang.ClassLoader 类,实现自己的类加载器。如果想保持双亲委派模型,就应该重写 findClass(name) 方法;如果想破坏双亲委派模型,可以重写 loadClass(name) 方法。

自定义 ClassLoader

JVM 中预置的 3 种 ClassLoader 只能加载特定目录下的 .class 文件,如果我们想加载其他特殊位置下的 jar 包或类时(比如,我要加载网络或者磁盘上的一个 .class 文件),默认的 ClassLoader 就不能满足我们的需求了,所以需要定义自己的 Classloader 来加载特定目录下的 .class 文件
被加载的类
image.png
类加载器
image.png
最后运行打印出结果:
image.png
PS:上述动态加载 .class 文件的思路,经常被用作热修复和插件化开发的框架中,包括 QQ 空间热修复方案、微信 Tinker 等原理都是由此而来。客户端只要从服务端下载一个加密的 .class 文件,然后在本地通过事先定义好的加密方式进行解密,最后再使用自定义 ClassLoader 动态加载解密后的 .class 文件,并动态调用相应的方法。

Android 中的 ClassLoader

在 Android 虚拟机里是无法直接运行 .class 文件的,Android 会将所有的 .class 文件转换成一个 .dex 文件,并且 Android 将加载 .dex 文件的实现封装在 BaseDexClassLoader 中,而我们一般只使用它的两个子类:PathClassLoaderDexClassLoader

PathClassLoader:用来加载系统 apk 和被安装到手机中的 apk 内的 dex 文件。它的 2 个构造函数如下:

image.png
参数说明:
dexPath:dex 文件路径,或者包含 dex 文件的 jar 包路径;(一般是已经安装应用的 apk 文件路径)
librarySearchPath:C/C++ native 库的路径

DexClassLoader:对比 PathClassLoader 只能加载已经安装应用的 dex 或 apk 文件,可以从 SD 卡上加载包含 class.dex 的 .jar 和 .apk 文件,这也是插件化和热修复的基础,在不需要安装应用的情况下,完成需要使用的 dex 的加载。

image.png
参数说明:
dexPath:包含 class.dex 的 apk、jar 文件路径 ,多个路径用文件分隔符(默认是“:”)分隔。
optimizedDirectory:用来缓存优化的 dex 文件的路径,即从 apk 或 jar 文件中提取出来的 dex 文件。该路径不可以为空,且应该是应用私有的,有读写权限的路径。

使用 DexClassLoader 实现热修复

修建一个module,编写接口以及实现
image.pngimage.png
将 Ioffer.java 和 OfferImpl.java 打包成 classes.jar,然后通过 dx 工具将生成的 classes.jar 包中的 class 文件优化为 dex 文件。
dx —dex —output=target.jar classes.jar
上述 target.jar 就是我们最终需要用作 hotfix 的 jar 包。
接下来,修改 HotFixActivity 中的逻辑,使用 DexClassLoader 加载 HotFix patch 中的 SayHotFix 类,如下:
image.png
修复前修复后
image.pngimage.png
Demo实例参考

以上为全部内容!