动态加载和类加载机制
参考:Android加壳脱壳学习(1)——动态加载和类加载机制详解-看雪
类加载器
Android中的类加载器机制与JVM一样遵循双亲委派模式
双亲委派模式
双亲委派模式定义
通俗来讲就是,加载.class文件是看自己这个“类加载器”有没有加载过,
没有就找上一级加载,
上一级没有就找上上级加载,
上上级没有就找上上上级加载,如果最终上上上级还是发现没有加载过,那这个时候就要加载了。
此时上上上级
使用findClass(name)尝试加载,
如果上上上级加载失败就叫上上级使用findClass(name)尝试加载,
如果上上级加载失败就叫上级使用findClass(name)尝试加载。
- 加载.class文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍
- 如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级ClassLoader如果没有加载过,则尝试加载,加载失败,则逐级向下交还调用者加载
双亲委派的作用
(1) 防止同一个.class文件重复加载
(2) 对于任意一个类确保在虚拟机中的唯一性。由加载它的类加载器和这个类的全类名一同确立其在Java虚拟机中的唯一性
(3) 保证.class文件不被篡改,通过委派方式可以保证系统类的加载逻辑不被篡改
![]() |
![]() |
Android中类加载机制
总结来讲:
核心就是Zygote进程,在解析了init.rc后会启动app_process进程,随后该进程调用ZygoteInit.main( )初始化Zygote。
AndroidRuntime.start()调用startvm创建Dalvik虚拟机,startreg注册JNI函数。建立socket通道。预加载类与资源。fork system_server进程。等待创建新进程的请求。
| 回顾Dalvik虚拟机 | 我们执行app_process程序,进入main函数里面 ,然后进行 AndroidRuntime::start |
|---|---|
![]() |
![]() |
| Zygote启动 | 然后进入Java层 |
|---|---|
![]() |
![]() |
Zygote总结:
(1)解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法
(2)调用AndroidRuntime的startVM()方法创建虚拟机,再调用startReg()注册JNI函数
(3)通过JNI方式调用ZygoteInit.main(),第一次进入Java世界
(4)registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求
(5)preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率
(6)通过startSystemServer(),fork system_server进程,也是Java Framework的运行载体(下面讲到system server再详细讲解)
(7)调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作
Android的类加载机制和JVM一样遵循双亲委派模式,在dalvik/art启动时将所有Java基本类和Android系统框架的基本类加载进来,预加载的类记录在/frameworks/base/config/preloaded-classes中。
因为所有进程都是通过Zygote fork出来的,Zygote在启动时加载了,后续所有进程在被fork出来时都会带有这些基本类。
类方法扫盲
接下来会提到一些加载类的方法,这里提前介绍下:
首先,博客里所有[Ff]or[Nn]ame都是forName,Java采用驼峰命名法,不存在其他错误的写法。
Q1: forName()、loadClass()、findClass() 的区别
Class.forName()
- 定义与作用:
Class.forName()用于动态加载指定全限定名的类到 JVM,并返回对应的Class对象- 默认情况下会触发类的初始化(即执行静态代码块)
- 提供重载方法
forName(String name, boolean initialize, ClassLoader loader),可控制是否初始化类及指定类加载器
- 与
new的区别:
new是静态绑定,直接实例化对象;forName()是动态加载,需通过反射(如newInstance())创建对象
ClassLoader.loadClass()
- 定义与作用:
loadClass()是类加载的核心方法,实现了双亲委派模型:- 检查类是否已加载
- 委派父类加载器加载
- 若父类无法加载,调用
findClass()加载
默认不初始化类,需显式调用resolveClass()或实例化对象时才会触发静态代码块
- 与
findClass()的关系:
loadClass()负责加载流程控制,findClass()提供具体加载实现开发者通常覆写findClass()而非loadClass(),以避免破坏双亲委派机制
ClassLoader.findClass()
- 定义与作用:
findClass()是ClassLoader类的受保护方法,需子类重写,用于根据类名查找并加载字节码 - 应用场景:
自定义类加载器时,通过覆写findClass()实现从非标准路径加载类(如加密类文件、热部署等)
Q2:他们不都是加载类吗?
类加载的生命周期分为:
- 加载(将
.class字节码读入内存,生成Class对象)、 - 链接(1.验证字节码合法性、2.为静态变量分配内存并赋默认值,如
int初始化为0、3.将符号引用转换为直接引用)、 - 初始化(执行
<clinit>方法,为静态变量赋真实值,执行静态代码块)。

| 方法 | 作用阶段 | 是否触发初始化 | 作用 |
|---|---|---|---|
ClassLoader.loadClass() |
加载 + 部分链接 | 默认否(需手动) | 管理双亲委派流程,确保类加载符合 JVM 规范 |
ClassLoader.findClass() |
仅完成 加载 | 否 | 仅实现从自定义来源(如加密文件、网络)读取字节码,不处理后续流程。 |
Class.forName() |
加载 + 链接 + 初始化 | 是(默认) | 封装了整个加载流程,直接交付可用的类 |
Class.forName 和 ClassLoader.loadClass加载有何不同:
- ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作)
- Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作
Q3:Class 与 ClassLoader 的区别
Class类
- 功能:
表示 JVM 中的类和接口,提供反射能力(如获取字段、方法、构造器等) - 关键方法:
forName()(动态加载类)、newInstance()(创建实例)、getDeclaredFields()(反射操作)等 - 与类加载的关系:
Class.forName()内部通过ClassLoader实现加载,但会触发类的初始化
ClassLoader类
- 功能:
抽象类加载器,负责从不同来源(如文件、网络)加载类的字节码,生成Class对象 - 关键方法:
loadClass()(加载流程控制)、findClass()(自定义加载逻辑)、defineClass()(字节码转Class对象) - 特点:
- 遵循双亲委派模型,避免重复加载
- 默认不初始化类,需显式调用
newInstance()才会触发静态代码块
结合上面的方法来理解的话,就是Class类使用forName方法包含了整个类加载,在这个过程中会调用ClassLoader的加载功能,ClassLoader是专门实现类加载的部分的,但是Class类还能实现其他的一些功能,比如反射、类型检查、创建实例等。
Q4:什么时候会加载类
Answer:
1.隐式加载时:
- 创建类的实例,也就是new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射Class.forName("android.app.ActivityThread")
- 初始化一个类的子类(会首先初始化子类的父类)
2.显式加载时: - 使用LoadClass()加载
- 使用forName()加载
Android类加载器层级关系及分析
![]() |
![]() |
| 类加载器 | 继承关系 | 功能 | 加载目标 | optimizedDirectory | 典型应用场景 | 引入版本 |
|---|---|---|---|---|---|---|
| BootClassLoader | ClassLoader的内部类 |
预加载Android系统核心类(如Activity、Context等) |
/system/framework下的核心类 |
无 | 系统启动预加载 | 所有Android版本 |
| PathClassLoader | BaseDexClassLoader |
加载已安装APK的类(/data/app目录)或系统类 |
已安装的APK文件 | 系统默认路径(/data/dalvik-cache) |
默认应用类加载 | 所有Android版本 |
| DexClassLoader | BaseDexClassLoader |
动态加载未安装的APK/JAR/DEX文件 | 任意路径的APK、JAR、DEX文件 | 需指定(应用私有目录) | 插件化、热修复 | 所有Android版本 |
| InMemoryDexClassLoader | BaseDexClassLoader |
直接从内存加载DEX数据(无需写入磁盘) | 内存中的DEX数据(ByteBuffer形式) |
无 | 动态生成代码的安全加载 | Android 8.0+ (API 26) |
| DelegateLastClassLoader | BaseDexClassLoader |
修改双亲委托机制:优先自行加载类,失败后再委托父加载器 | 任意路径的类文件 | 需指定 | 覆盖系统类行为(如动态补丁) | Android 8.1+ (API 27) |
<1> BootClassLoader
BootClassLoader 是启动类加载器,用于加载 Zygote 进程在缓存中已经预加载的基本类。当在缓存中无法获取到类时,BootClassLoader 会直接调用自己的 findClass方法来加载类,而调用 findClass方法实际上就是调用了Class.classforName方法。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
由于它是基类 ClassLoader 的一个内部类,具有包访问权限,应用程序无法直接访问 BootClassLoader。
在 ZygoteInit.preloadClasses() 方法中,通过指定 BootClassLoader 作为类加载器来使用 Class.forName() 加载基本类,该方法触发类初始化,此后应用程序就可以通过 Class.forName()来加载这些已经预加载的基本类。

上图中体现了ZygoteInit.preload()方法使用的是Bootloader,而无论是系统类加载器(PathClassLoader)还是自定义的类加载器(DexClassLoader),最顶层的祖先加载器默认是 BootClassLoader,与 JVM 一样,保证了基本类的类型安全
<2> PathClassLoader
加载已安装apk的类和系统类
PathClassLoader 是作为应用程序的系统类加载器,也是在 Zygote 进程启动的时候初始化的
基本流程为:Zygote启动 → 预加载系统资源 → 创建SystemServer进程时初始化PathClassLoader → 应用进程fork继承类加载器 → 默认使用PathClassLoader加载已安装APK的dex。
所以每一个 APP 进程从 Zygote 中 fork 出来之后都自动携带了一个 PathClassLoader,它通常用于加载 已安装的 apk 里面的 .dex 文件
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
<3> DexClassLoader
可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载, 但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多热修复和插件化方案都是采用DexClassLoader
public class
DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
总结:
我们可以发现DexClassLoader与PathClassLoader都继承于BaseDexClassLoader,这两个类只是提供了自己的构造函数,没有额外的实现
区别:
DexClassLoader提供了optimizedDirectory,而PathClassLoader则没有,optimizedDirectory正是用来存放odex文件的地方,所以可以利用DexClassLoader实现动态加载
<4> BaseDexClassLoader
BaseDexClassLoader派生出两个子类加载器:PathClassLoader和DexClassLoader
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList; //记录dex文件路径信息
/*
* 构造函数 初始化 DexPathList,用来查找Dex、SO库的路径
* dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割;
* optimizedDirectory: 优化后dex文件存在的目录, 可以为null;
* libraryPath: native库所在路径列表;当有多个路径则采用:分割;
* ClassLoader:父类的类加载器
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

上图中可以看出,BaseDexClassLoader的相关流程:
- 根据 传参
dexPath确定Dex文件路径 - 找到了就加载Dex,根据optimizedDirectory(odex的存放目录)调用不同的DexClassLoader/PathClassLoader来加载Dex文件
- 执行 openDexFileNative(oat同理)加载odex
- 解析
dexPath中的 DEX 文件路径 - 检查
optimizedDir目录下是否存在有效的 ODEX 文件。- ODEX 有效:直接加载现有的 ODEX 文件。
- ODEX 无效或不存在:触发优化流程,调用dexopt。
- 调用 dexopt
- 验证 DEX 文件的合法性(如签名、字节码结构)
- 优化 DEX 为 ODEX(指令替换、字节码对齐等)。
- 将 ODEX 写入
optimizedDirectory目录
- 调用 dexopt
- 加载 ODEX
openDexFileNative将生成的 ODEX 文件通过mmap映射到内存。- 返回一个
mCookie(整型句柄),供后续类查找使用。
- 解析









浙公网安备 33010602011771号