相关复习:
面试:热修复原理_沙漠一只雕得儿得儿的博客-CSDN博客_android 热修复原理面试
面试:类的初始化过程_沙漠一只雕得儿得儿的博客-CSDN博客
类加载器(Class Loader):顾名思义,指的是可以加载类的工具。前面
JVM类加载机制——类的生命周期已经介绍过类加载的5个过程,即:加载、验证、准备、解析、初始化,而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,启动(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器),下面分别介绍。
启动类加载器主要加载的是JVM自身需要的类,这个类加载使用 C++语言实现 (这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的)的,是虚拟机自身的一部分,它负责将
扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载
也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。

Java 类加载是一种委托机制(parent delegate),即:除了顶级启动类加载器(bootstrap classloader)之外,每个类加载器都有一个关联的上级类加载器(parent 字段)。当一个类加载器准备执行类加载时,它首先会委托给上级加载器去加载,而上级加载器可能还会继续向上委托,递归这个过程。如果上级构造器无法加载,才会返回由自己加载。
该方法加载指定名称(包括包名)的二进制类型,该方法在JDK1.2之后不再建议用户重写但用户可以直接调用该方法,loadClass()方法是ClassLoader类自己实现的,该方法中的逻辑就是双亲委派模式的实现
- protected Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException {
- synchronized (getClassLoadingLock(name)) {
- // ① 先从缓存查找该class对象,找到就不用重新加载
- Class<?> c = findLoadedClass(name);
- if (c == null) {
- long t0 = System.nanoTime();
- try {
- 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
- }
-
- if (c == null) {
- // If still not found, then invoke findClass in order
- // ④如果都没有找到,则通过自定义实现的findClass去查找并加载
- c = findClass(name);
- }
- }
- if (resolve) {//是否需要在加载时进行解析
- }
- return c;
- }
- }
dalvik与jvm的不同:
1、jvm:执行的是class文件;dalvik:执行的是dex
2、类加载系统与jvm区别较大
3、jvm只能存在一个;dalvik可以同时存在多个dvm
4、dalvk基于寄存器,jvm基于栈(内存)
ART对比dalvik:
1、DVM使用JIT将字节码转换成机器码,效率低
2、ART采用AOT预编译技术,执行效率更快
3、ART会占用更多的安装时间和存储空间
4、预编译减少了 CPU 的使用频率,降低了能耗 JIT(Just In Time,即时编译技术) AOT(Ahead Of Time,预编译技术)
Android的Dalvik/ART虚拟机虽然与标准Java的JVM虚拟机不一样,ClassLoader具体的加载细节不一样,但是工作机制是类似的, (从android sdk的ClassLoader#loadClass和jdk的ClassLoader#loadClass源码可以看出都使用了双亲委托机制)
这一节我们就来分析 Android ART 虚拟机 中的类加载器:
| ClassLoader 实现类 | 作用 |
|---|---|
| BootClassLoader | 加载 SDK 中的类 |
| PathClassLoader | 加载应用程序的类 |
| DexClassLoader | 加载指定的类 |

BootClassLoader实例在Android系统启动的时候被创建,用于加载一些Android系统框架的类,其中就包括APP用到的一些系统类。(与Java中的BootstrapClassLoader不同,它并不是由C/C++代码实现,而是由Java实现的,BootClassLoader是ClassLoader的内部类)
我们先来看看基类BaseDexClassLoader的构造方法
- public BaseDexClassLoader(String dexPath, File optimizedDirectory,
- String librarySearchPath, ClassLoader parent) {
- super(parent);
- this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
- }
BaseDexClassLoader构造方法的四个参数的含义如下:
| 参数 | 描述 |
|---|---|
| dexPath | 加载 dex 文件的路径 |
| optimizedDirectory | 加载 odex 文件的路径 |
| librarySearchPath | 加载 so 库文件的路径 |
| parent | 上级类加载器 |
它派生出两个子类加载器:
DexClassLoader与PathClassLoader都继承于BaseDexClassLoader,这两个类只是提供了自己的构造函数,没有额外的实现,我们对比下它们的构造函数的区别。
从源码可以看出,PathClassLoader & DexClassLoader 其实都没有重写方法,所以主要的逻辑还是在 BaseDexClassLoader。
这两个类其实只有一点不同,在 Android 9.0 之前,DexClassLoader 的构造方法需要传入第二个参数optimizedDirectory,这个路径是存放优化后的 dex 文件的路径(odex)。
不过在 Android 9.0 之后,DexClassLoader 也不需要传这个参数了。
DexClassLoader.java - Android 8.0
- public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
- super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
- }
DexClassLoader.java - Android 9.0
- public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
- super(dexPath, null, librarySearchPath, parent);
- }
- public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
- super(dexPath, null, librarySearchPath, parent);
可以发现这两个类的构造函数最大的差别就是DexClassLoader提供了optimizedDirectory,而PathClassLoader则没有,optimizedDirectory正是用来存放odex文件的地方,所以可以利用DexClassLoader实现动态加载。

我们提到 BaseDexClassLoader 将 findClass() 的任务委派给 DexPathList 对象处理,这一节我们就来分析 DexPathList 里的处理过程。
DexFile 是 dex 文件在内存中的映射
Elment - dexFile
Element[] dexElements 一个app所有的class文件都在 dexElements里
Android虚拟机有两个主要的类加载器DexClassLoader与PathClassLoader,它们都继承于BaseDexClassLoader,它们内部都维护了一个DexPathList的对象,DexPathList主要用来存放指明包含dex文件、native库和优化odex目录。 Dex文件采用DexFile这个类来描述,Dex的加载以及类的查找都是通过DexFile调用它的native方法来完成的。
- 1、宿主类加载器
- ClassLoader appClassLoader = context.getClassLoader();
- 宿主 DexPathList
- Object appPathList = pathListField.get(appClassLoader);
- 宿主 dexElements
- Object[] appDexElements = (Object[]) dexElementsField.get(appPathList);
-
- 2、插件加载器
- ClassLoader pluginClassLoader = new DexClassLoader(apkPath,
- context.getCacheDir().getAbsolutePath(),
- null,
- appClassLoader);
- 插件 DexPathList
- Object pluginPathList = pathListField.get(pluginClassLoader);
- 插件 dexElements
- Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
-
- 3、合并 dexElements
- // Object[] obj = new Object[appDexElements.length + pluginDexElements.length]; // x
-
- Object[] newElements = (Object[]) Array.newInstance(appDexElements.getClass().getComponentType(),
- appDexElements.length + pluginDexElements.length);
- System.arraycopy(appDexElements, 0, newElements, 0, appDexElements.length);
- System.arraycopy(pluginDexElements, 0, newElements, 0, pluginDexElements.length);
-
- 4、赋值
- dexElementsField.set(appPathList, newElements);

加载的字节码不同 Android虚拟机运行的是dex字节码,Java虚拟机运行的class字节码。
类加载器不同以及类加载器的类体系结构不同 如上面的类加载器结构图
BootClassLoader和Java的BootStrapClassLoader区别:Android虚拟机中BootClassLoader是ClassLoader内部类,由java代码实现而不是c++实现,是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见,所以我们没法使用。 Java虚拟机中BootStrapClassLoader是由原生代码(C++)编写的,负责加载java核心类库(例如rt.jar等)