前言
Linus Benedict Torvalds : RTFSC – Read The Funning Source Code
概述
Classloader 类加载器,用来加载Java类到 Java 虚拟机中的一种加载器。Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。
Java ClassLoader
我们先从Java的ClassLoader开始学习。
Java语言系统自带有三个类加载器:
- Bootstrap ClassLoader : 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
- Extention ClassLoader : 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
- Appclass Loader : 也称为SystemAppClass 加载当前应用的classpath的所有类。
这三个类的继承关系如下:
加载顺序
Jvm 启动的加载顺序是 Bootstrap ClassLoader->Extention ClassLoader->Appclass Loader。
首先 Bootstrap ClassLoader 是在Jvm刚起动的时候去加载java核心API的。
然后 ExtClassLoader 加载扩展API。
最后 AppClassLoader 加载CLASSPATH目录下定义的Class。
父加载器
每个类加载器都有一个父加载器。这个也跟之后的双亲委托模型相关。
通过 ClassLoader cl = Test.class.getClassLoader();
我们可以获取关于这个类的ClassLoader。
一般的用户类的ClassLoader都是由AppClassLoader
。而AppClassLoader
的父加载器则是ExtClassLoader
。但ExtClassLoader
的父加载器则是null,这并不是因为它没有父加载器,而是因为父加载器是Bootstrap ClassLoader
,Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,所以想要直接获取都会拿到null。
如果我们看 int.class 和 string.class 的父加载器,都会是获取为null。
父加载器的实现主要在创建 ClassLoader 对象的时候,有两种方法:
- 外部类创建 ClassLoader 时指定一个 ClassLoader 为其父加载器。
- 如果没有指定父加载器,则默认的父加载器一律为 AppClassLoader。
而 AppClassLoader 的父加载器是 ExtClassLoader 主要是因为在创建时就指定好了。
双亲委托
一个类加载器查找 class 和 resource 时,通过“委托模式”进行的,它首先判断这个 class 是不是在缓存里已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。
通过双亲委托的方式,极大可能地节省加载资源,只要父亲节点加载过该类,其他子节点就不用再进行加载,从而在内存中就只有一份字节码。这样的机制也可以提升安全性。当某些恶意人员企图通过在其 ClassLoader 中修改系统 Class 文件时,就会因为双亲委派机制而无法加载。
ps.JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。
核心流程
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
核心的加载方法从 loadclass 为入口。
|
|
从代码上可以看出双亲委托的主要流程,也可以看到当获取父加载器为null的时候,系统会默认为我们指定到 Bootstrap ClassLoader 来加载。
Android ClassLoader
通过学习 JVM 的 ClassLoader ,我们大致了解了 JVM 的加载,现在可以开始了解下 Android 的 ClassLoader。
Android 的 ClassLoader 跟 JVM 的 ClassLoader 原理上一致,但最终实现不同。
Android 的虚拟机是 Davlik 或者 ART,具体执行的不是 Class 文件,而是针对移动设备进行优化后的 DEX 文件。当然 Android 系统也提供了类似的加载机制,主要是由父类是BaseDexClassLoader
的两个子类PathClassLoader
和DexClassLoader
来完成。
加载顺序
Android Apk启动的加载顺序是 BootClassLoader->PathClassLoader\DexClassLoader。
首先 BootClassLoader 是在Android刚起动的时候去加载一些系统Framework层级需要的类。
然后 PathClassLoader\DexClassLoader 加载Apk的应用类。
结构
下图是一张 Android ClassLoader 的类图:
从图中可以了解到android 的类加载器总共有四个:BootClassLoader
,URLClassLoader
,PathClassLoader
,DexClassLoader
。
Android 和 JVM 的类加载不同。
从上面的代码可以发现,无论在构造的时候传不传父加载器,android的ClassLoader都必须要有一个父加载器。Android在默认无父加载器传入的情况下,默认父加载器为PathClassLoader
且此PathClassLoader
父加载器为BootClassLoader
。
BootClassLoader
BootClassLoader 是 ClassLoader 内部类,由java代码实现而不是c++实现,是Android平台上所有 ClassLoader 的最终 parent。
URLClassLoader
只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。
BaseDexClassLoader
PathClassLoader 和 DexClassLoader 都继承自 BaseDexClassLoader,其中的主要逻辑都是在BaseDexClassLoader完成。
其主要的构造函数如下:
主要的四个参数含义:
参数 | 含义 |
---|---|
dexPath | String: 包含类和资源的jar和apk文件列表,由File.pathSeparator分隔,在Android上默认为“:”。 |
optimizedDirectory | File:此参数已弃用,不起作用 |
librarySearchPath | String: 包含native libraries的目录列表,由File.pathSeparator分隔;可能为null。 |
parent | ClassLoader: 父加载器。 |
DexClassLoader
DexClassLoader支持加载APK、DEX和JAR,也可以从SD卡进行加载。因为实际调用的是BaseDexClassLoader方法,而BaseDexClassLoader里对”.jar”,”.zip”,”.apk”,”.dex”后缀的文件都会生成一个对应的dex文件,所以DexClassLoader支持加载jar而URLClassLoader不能。
PathClassLoader
PathClassLoader用来加载Android系统类和应用的类,并且不建议开发者使用。在dalvik虚拟机上,PathClassLoader只能用来加载已安装apk的dex。而在art虚拟机上PathClassLoader可以加载未安装的apk的dex,但还是不建议用,有坑。
双亲委托
Android 的加载和 java 的加载函数不一样,JVM中ClassLoader通过defineClass方法加载jar里面的Class,而Android中是loadClass方法。
|
|
可以看到这里是很典型的跟jvm一样的双亲委托模型。
具体流程如下:
- 先查询当前ClassLoader实例是否加载过此类,有就返回。
- 没有查询到,就查询Parent中是否已经加载过此类,如果已经加载过,就直接返回Parent加载的类。
- 如果继承路线上的ClassLoader都没有加载,才由Child执行类的加载工作。
并且同样的,每个层级的类都被当作是不同的类,杜绝了冒充核心类库的问题。
简单实现
首先我们随便创建一个工程:test.apk
然后在我们的主工程里面调用它:
总结
无论是Android 还是 Java 的classloader都比较简单,分析起来不复杂,但是要运用到工程中的话还是会有相应的难度,在android 中最大的难度是解决三点:1. 资源访问;2. 四大组件生命周期;3. 插件的管理。这几个问题解决好才能真正在Android上运用。