1. 前言
类加载原理作为程序运行的基础,一直在程序的背后默默的付出。如今Android
中的插件化、热修复等动态加载技术的实现也都涉及到了类加载的原理。关于类加载的相关知识我以前也是遇到一点看一点,没有完整的详细的了解过,最近有时间专门对这块知识进行了学习,于是这里做一个总结。
2. 类加载过程
一个类从.class
文件被加载到内存,到在内存中使用,最后从内存中卸载,这是一个完整的生命周期过程。不过在获得.class
文件之前,我们编码时的文件格式还是.java
文件格式,还记得刚学Java
时学到过在完成编码之后要先执行javac
命令进行编译,编译生成对应的.class
文件,之后再通过java
命令执行Java
程序。不过当时只知道是先编译再运行,并不知道到底是怎么运行的谁去运行的。
其实一个类从.class
文件被加载到内存到从内存中卸载,整个生命周期一共经过以下几个阶段:
- 加载
- 连接(包含验证、准备、解析三个阶段)
- 初始化
- 使用
- 卸载
2.1 加载阶段
在加载阶段虚拟机主要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。
这个简单的来说加载阶段主要就是将类的.class
文件作为二进制字节流读入内存,并且在内存中实例化一个java.lang.Class
对象以便后续访问。在这个阶段中.class
文件二进制字节流的读取来源没有太多限制,可以非常灵活。比如可以从本地系统中读取、可以从jar
包中读取、可以从网络下载等等。
2.2 连接—验证阶段
验证是连接阶段中的第一步,主要的作用是保证Class
文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。在验证阶段大致会完成以下四个检验操作:
- 文件格式验证:验证字节流是否符合
Class
文件格式的规范,并且能被当前版本的虚拟机处理。 - 元数据验证:对字节码描述的信息进行语义分析,保证其描述符合
Java
语言规范的要求。 - 字节码验证:通过数据流和控制流分析,确定程序逻辑是合法的、符合罗急的。
- 符号引用验证:对类自身以外的信息进行匹配性校验。
从以上几个操作可以看出,这个阶段主要就是将二进制字节流进行一个合法验证,包括文件格式、语义、数据流控制流和符号引用等。保证不会出现类似文件格式错误、继承了被final
修饰的类、指令跳转错误、类型转换错误、修饰符访问性等等错误情况。
2.3 连接—准备阶段
准备阶段中主要是为类中静态变量在方法区里分配内存并且设置类变量的初始值。这里的初始值即零值。具体如下:
数据类型 | 零值 |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
char | '\u0000' |
byte | (byte)0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
2.4 连接—解析阶段
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调试限定符7类符号引用进行。
2.5 初始化阶段
初始化阶段中真正开始执行类中定义的Java
程序代码,为所有类变量进行赋值,执行静态代码块。
3. Java中类加载器
从上面的类加载过程可以看出在初始化阶段以前除了加载阶段,其余阶段都是由虚拟机主导控制。而在加载阶段可以通过自定义类加载器进行参与。类加载器顾名思义是用来加载类的,负责将类的.class
文件转换成内存中类的二进制字节流。Java
中的类加载器按类型分有两种:系统类加载器和自定义类加载器。其中系统类加载器有主要有三种,分别是:引导类加载器(Bootstrap ClassLoader
)、拓展类加载器(Extensions ClassLoader
)和应用程序类加载器(Application ClassLoader
)。
3.1 系统类加载器
3.1.1 引导类加载器(Bootstrap ClassLoader
)
这个类加载器是用C/C++
语言实现的,用来加载JDK
中的核心类,主要加载$JAVA_HOME/jre/lib
目录下的类,例如rt.jar
、resources.jar
等包中的类。
3.1.2 拓展类加载器(Extensions ClassLoader
)
这个类加载器是用Java
语言实现的,实现类为ExtClassLoader
,用来加载Java
的拓展类,主要加载$JAVA_HOME/jre/lib/ext
目录和系统属性java.ext.dir
所指定的目录。
3.1.3 应用程序类加载器(Application ClassLoader
)
这个类加载器是用Java
语言实现的,实现类为AppClassLoader
,可以通过ClassLoader.getSystemClassLoader
方法获取到,主要加载Classpath
目录和系统属性java.class.path
指定的目录下的类。
3.1.4 ClassLoader的继承关系
public class ClassLoaderDemo {
public static void main(String[] args) {
ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
while (classLoader != null) {
System.out.println("ClassLoader:" + classLoader);
classLoader = classLoader.getParent();
}
}
}
这里新建一个Java
类ClassLoaderDemo
,循环打印其类加载器和父类加载器,控制台输出结果如下。
从结果可以看出ClassLoaderDemo
类的类加载器是AppClassLoader
,AppClassLoader
的父类加载器是ExtClassLoader
,而ExtClassLoader
的父类加载器就为null
了,这是因为ExtClassLoader
的父类加载器BootstrapClassLoader
是由C/C++
语言实现的,所以在Java
中无法获取到它的引用。接下来再进入源码来看一下,先看AppClassLoader
。
static class AppClassLoader extends URLClassLoader {
......
}
查看源码发现AppClassLoader
的父类并不是ExtClassLoader
而是URLClassLoader
,再看ExtClassLoader
。
static class ExtClassLoader extends URLClassLoader {
......
}
ExtClassLoader
的父类也是URLClassLoader
进而再看URLClassLoader
。
public class URLClassLoader extends SecureClassLoader implements Closeable {
......
}
public class SecureClassLoader extends ClassLoader {
......
}
public abstract class ClassLoader {
......
}
URLClassLoader
的父类是SecureClassLoader
,而SecureClassLoader
的父类是ClassLoader
,ClassLoader
是一个抽象类。通过对源码的跟踪发现,似乎这里的继承关系与控制台输出的结果不太一致。于是进一步去看输出ClassLoaderDemo
中调用的ClassLoader.getParent()
方法源码。
public final ClassLoader getParent() {
if (parent == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(parent, Reflection.getCallerClass());
}
return parent;
}
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
ClassLoader
的getParent
方法中看到返回的是一个成员变量中的parent
,他是一个ClassLoader
类型对象。继续跟踪寻找它是在哪里初始化赋值的。
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
跟踪查看源码发现父类加载器parent
是在ClassLoader
的构造函数时传入的,如果没有传入默认调用getSystemClassLoader
方法获取一个父类加载器。接下来继续查看getSystemClassLoader
方法。
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
getSystemClassLoader
方法中又调用了initSystemClassLoader
方法初始化系统类加载器,方法最后将这个类加载器scl
返回。继续查看initSystemClassLoader
方法。
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
.......
}
这个方法中获取到Launcher
之后调用了Launcher
的getClassLoader
方法获取到创建的类加载器,于是再到Launcher
中查看。
public ClassLoader getClassLoader() {
return this.loader;
}
Launcher
的getClassLoader
方法中返回了其成员变量中的loader
对象,于是再去寻找这个对象的创建。
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
......
}
在Launcher
的构造函数里找到,这里是通过Launcher.AppClassLoader.getAppClassLoader(var1)
方法创建的loader
,于是再进入查看。
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
getAppClassLoader
方法中最终new
了一个AppClassLoader
返回,这就又回到了AppClassLoader
的构造方法。之前看过构造传入的第二个参数就是parent
父类加载器,这里传入的var0
可以看到就是ExtClassLoader
类型。
总结来说ClassLoader
的父子关系并不是由继承实现的,A
是B
的父类加载器,并不表示B
继承了A
,每个CLassLoader
中保存了一个它的父类加载器的引用,通过getParent
方法获得的就是它的值,它是在类加载器创建时构造函数中进行赋值的。如果构造中没有传入父类加载器默认调用getSystemClassLoader
方法获取系统类加载器,通过查看发现默认系统类加载器就是AppClassLoader
。在Launcher
的构造方法中,会依次创建ExtClassLoader
和AppClassLoader
,此时ExtClassLoader
作为父类加载器由构造函数传入AppClassLoader
。实际的类加载继承关系如下图。
3.2 自定义类加载器
在一些特殊需求场景下可能需要程序员自定义类加载器。例如从网络下载一个加密过的.class
类文件,此时就需要自定义类加载器,先进行文件解密再进行类加载。下面就来模拟一下这个例子,先定义一个测试类TestPrint
。
public class TestPrint {
public void printString() {
System.out.println("测试输出字符串");
}
}
使用javac
命令编译生成TestPrint.class
文件。
Base64
加密,将编译生成的.class
文件转成二进制字节流加密后再保存成本地文件。
public class Test {
public static void main(String[] args) {
byte[] classBytes = FileIOUtils.readFile2BytesByStream("/Users/sy/Downloads/ClassLoader/TestPrint.class");
FileIOUtils.writeFileFromBytesByStream("/Users/sy/Downloads/ClassLoader/TestPrint.class",Base64.getEncoder().encode(classBytes));
}
}
得到加密后的TestPrint.class
后接下来编写自定义的类加载器MyClassLoader
。
public class MyClassLoader extends ClassLoader {
private String path;
protected MyClassLoader(String path) {
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class aClass = null;
// 获取二进制字节流
byte[] classBytes = loadClassBytes(name);
if (classBytes == null) {
System.out.println("class data is null");
} else {
aClass = defineClass(name, classBytes, 0, classBytes.length);
}
return aClass;
}
private byte[] loadClassBytes(String name) {
String fileName = getFileName(name);
File file = new File(path, fileName);
InputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
inputStream = new FileInputStream(file);
outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
return outputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private String getFileName(String name) {
String fileName;
int index = name.lastIndexOf(".");
if (index == -1) {
fileName = name + ".class";
} else {
fileName = name.substring(index + 1) + ".class";
}
return fileName;
}
}
自定义类加载器分为以下几个步骤:
- 继承抽象类
ClassLoader
。 - 复写
findClass
方法。 - 在
findClass
方法中获取到二进制字节流后,调用defineClass
方法。
在MyClassLoader
的findClass
方法中首先读取到本地硬盘下的TestPrint.class
文件的字节流,然后调用defineClass
方法,该方法会将字节流转化为Class
类型。最后写一个测试类调用。
public class TestMyClassLoader {
public static void main(String[] args) {
// 初始化类加载器
MyClassLoader myClassLoader = new MyClassLoader("/Users/sy/Downloads/ClassLoader");
try {
// 使用自定义类加载器获取Class
Class<?> printTest = myClassLoader.loadClass("TestPrint");
// 创建实例
Object instance = printTest.newInstance();
System.out.println("classloader:" + instance.getClass().getClassLoader());
Method method = printTest.getDeclaredMethod("printString", null);
method.setAccessible(true);
method.invoke(instance, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
此时发生错误是因为做了Base64
加密,需要在defineClass
前进行一个解码操作,修改findClass
方法。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class aClass = null;
// 获取二进制字节流
byte[] classBytes = loadClassBytes(name);
// Base64 decode
classBytes = Base64.getDecoder().decode(classBytes);
if (classBytes == null) {
System.out.println("class data is null");
} else {
aClass = defineClass(name, classBytes, 0, classBytes.length);
}
return aClass;
}
再次运行查看结果:
此时程序就能正常的执行,调用类中的方法了。4. 双亲委托
4.1 双亲委托模式
学习类加载器就避免不了要了解双亲委托,它是类加载器寻找加载类的模式。还是先看到之前自定义类加载器的例子,自定义时复写了findClass
方法,但是使用时却没有直接调用这个方法,使用时是通过ClassLoader.loadClass
方法获得Class
的。双亲委托模式就是在这个方法中实现的,于是进入查看源码。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查该类是否加载过
Class<?> c = findLoadedClass(name);
// c为空说明没有加载过
if (c == null) {
long t0 = System.nanoTime();
try {
// 判断是否有父类加载器
if (parent != null) {
// 有父类加载器就调用父类加载器的loadClass方法
c = parent.loadClass(name, false);
} else {
// 没有父类加载器就调用这个方法
// 方法中会调用native方法findBootstrapClass使用BootstrapClassLoader检查该类是否已加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 走到此处c还为null说明父类加载器没有加载该类
if (c == null) {
long t1 = System.nanoTime();
// 就调用自身的findClass查找加载该类
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
loadClass
中的代码逻辑非常清晰的描述了双亲委托模式,首先通过findLoadedClass
方法检验要加载的类是否被加载过。加载过直接返回该类的Class
对象,没加载过则c
为空进入下面的判断,判断父类加载器parent
是否为空,不为空调用父类的loadClass
方法,为空则调用findBootstrapClassOrNull
方法,该方法中会继续调用native
方法findBootstrapClass
使用BootstrapClassLoader
检查该类是否已加载。接着如果没有加载该类就会调用自身的findClass
方法查找加载该类。
最后对双亲委托模式做个总结:双亲委托模式是指类加载器加载一个类首先是判断这个类是否加载过,没加载过不会自己直接加载,而是委托给其父类加载器去查找,一直委托到顶层引导类加载器BootstrapClassLoader
,如果BootstrapClassLoader
找到该Class
就会直接返回,没找到就会交给子类加载器依次向下查找,一直没找到最后就会交给自身去查找。
4.2 双亲委托模式的好处
双亲委托模式有两个好处:
- 避免了类的重复加载,一个类如果加载过一次就不需要再次加载了,而是直接读取已经加载的
Class
。 - 类随着它的类加载器一起具备了一种带优先级的层次关系,使得更加安全。例如加载
java.lang.Object
类无论使用哪个类加载器加载,最终都会委托给顶层BootstrapClassLoader
来加载,这样保证了Object
类永远是同一个类,不会出现多个不同的Object
类。这样也无法通过自定义一个Object
类替换系统原来的Object
类。
另外Java
虚拟机判断两个类是同一个类,是依据两个类类名一致,并且被同一个类加载器加载。这里可以在之前的自定义类加载器的基础上测试下。将原来的MyClassLoader
复制后重命名YourClassLoader
一份,再编写一个测试类。
public class TestDifferentClassLoader {
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader("/Users/sy/Downloads/ClassLoader/");
YourClassLoader yourClassLoader = new YourClassLoader("/Users/sy/Downloads/ClassLoader/");
try {
Class<?> myPrintTest = myClassLoader.loadClass("TestPrint");
// 使用不同的类加载器
Class<?> yourPrintTest = yourClassLoader.loadClass("TestPrint");
Object myInstance = myPrintTest.newInstance();
Object yourInstance = yourPrintTest.newInstance();
System.out.println(myInstance.getClass().equals(yourInstance.getClass()));
System.out.println(myInstance.getClass().getName());
System.out.println(yourInstance.getClass().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
修改一下用同一个类加载器。public class TestDifferentClassLoader {
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader("D:\\");
YourClassLoader yourClassLoader = new YourClassLoader("D:\\");
try {
Class<?> myPrintTest = myClassLoader.loadClass("TestPrint");
// 用同一个类加载器
Class<?> yourPrintTest = myClassLoader.loadClass("TestPrint");
Object myInstance = myPrintTest.newInstance();
Object yourInstance = yourPrintTest.newInstance();
System.out.println(myInstance.getClass().equals(yourInstance.getClass()));
System.out.println(myInstance.getClass().getName());
System.out.println(yourInstance.getClass().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
5. Android中类加载器
Android
中类加载器与Java
中的类加载器类似但是并不完全相同。Java
中类加载器是加载.class
文件,而Android
中是加载的dex
文件,不过Android
中也分系统类加载器和自定义类加载器,系统类加载器主要包括以下三种:BootClassLoader
、DexClassLoader
和PathClassLoader
。这里还是先循环打印一下Android
中的父类加载器。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader classLoader = MainActivity.class.getClassLoader();
while (classLoader != null) {
Log.d("classLoader", "name:" + classLoader);
classLoader = classLoader.getParent();
}
}
}
运行日志结果:
从日志可以看到这里有两个类加载器,一个是BootClassLoader
另一个是PathClassLoader
。Android
中的每个类加载器里同样保存一个父类加载器的引用,getParent
方法获取到的同样是这个ClassLoader
,还是先来看一下Android
中的ClassLoader
类。
public abstract class ClassLoader {
private final ClassLoader parent;
......
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 检查该类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 父类加载器不为空则调用其的loadClass方法
c = parent.loadClass(name, false);
} else {
// 父类加载器为空就调用findBootstrapClassOrNull方法
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 父类加载器没有查找到该类,则调用自身findClass方法查找加载
c = findClass(name);
}
}
return c;
}
private Class<?> findBootstrapClassOrNull(String name)
{
return null;
}
public final ClassLoader getParent() {
return parent;
}
......
}
Android
中的ClassLoader
类同样是一个抽象类,它的loadClass
方法中的逻辑和Java
中的类似,同样是遵循了双亲委托模式,父类加载器不为空则调用父类加载器的loadClass
方法,为空则调用findBootstrapClassOrNull
方法,该方法这里直接返回的null
。若父类加载器没有查找到需要加载的类,则调用自身的findClass
方法。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
接着看到ClassLoader
中的findClass
里是直接抛出了一个ClassNotFoundException
异常,说明这个方法需要子类来实现。那么接下来就来看看Android
中类加载器的实际继承关系。
抽象类ClassLoader
定义了类加载器的主要功能,它的子类如上图。先根据源码梳理下它们之间的关系。
public class BaseDexClassLoader extends ClassLoader {
......
}
public class SecureClassLoader extends ClassLoader {
......
}
public class DexClassLoader extends BaseDexClassLoader {
......
}
public class PathClassLoader extends BaseDexClassLoader {
......
}
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
......
}
public class URLClassLoader extends SecureClassLoader implements Closeable {
......
}
public final class DelegateLastClassLoader extends PathClassLoader {
......
}
除了这些子类,抽象类ClassLoader
中还有一个内部类BootClassLoader
。
class BootClassLoader extends ClassLoader {
......
}
综上所述Android
中类加载器的继承关系如下图。
接下来简单了解一下这些类加载器。
5.1 BootClassLoader
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
public BootClassLoader() {
super(null);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
......
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findClass(className);
}
return clazz;
}
......
}
BootClassLoader
是ClassLoader
的内部类继承自ClassLoader
,并且提供了一个获取单例的getInstance
方法。与Java
中不同BootClassLoader
不是用C/C++
实现的是用Java
实现的,Android
系统启动时会使用BootClassLoader
来预加载常用类。
5.2 DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
DexClassLoader
看名字就知道是用来加载dex
文件的,它的构造函数中有四个参数:
- dexPath:表示
dex
相关文件路径集合。 - optimizedDirectory:表示解压的
dex
文件存储路径。 - librarySearchPath:包含
C/C++
库的路径集合。 - parent:父类加载器。
DexClassLoader
继承自BaseDexClassLoader
其主要的方法都在其父类中实现。
5.3 PathClassLoader
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);
}
}
PathClassLoader
是用来加载系统类和应用程序的类,同样继承自BaseDexClassLoader
类, 它的构造函数里没有optimizedDirectory
参数,通常用来加载已经安装的dex
文件。
5.4 BaseDexClassLoader
BaseDexClassLoader
是DexClassLoader
和PathClassLoader
的父类。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
......
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
if (reporter != null) {
reporter.report(this.pathList.getDexPaths());
}
}
......
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
.......
}
在它的构造方法中先是创建了一个DexPathList
对象,接着它的findClass
方法中是通过pathList.findClass
方法获得的Class
对象。
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
findClass
方法中循环遍历了dexElements
数组,再通过Element.findClass
方法来获得Class
对象,Element
又是DexPathList
的内部类,进一步找到它的findClass
方法查看。
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
这个方法中又调用了DexFile
的loadClassBinaryName
方法。
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
loadClassBinaryName
方法里继续调用了defineClass
方法,而defineClass
方法里最终调用了defineClassNative
这个native
方法加载dex
文件。
Android
系统启动后init
进程会启动Zygote
进程,Zygote
进程初始化时会预加载常用类,进而会调用Class.forName
方法,该方法里会通过BootClassLoader.getInstance
方法创建BootClassLoader
。之后Zygote
进程又会启动SystemServer
进程,进而又会通过PathClassLoaderFactory
创建PathClassLoader
。总而言之,Android
中的这两个类加载器在启动时就进行初始化创建了。
6. 总结
Java
中类加载生命周期包括:加载、连接、初始化、使用、卸载这些阶段,其中连接阶段又分为验证、准备、解析。Java
中的 系统类加载器与Android
中的系统类加载器并不相同,但都遵循了双亲委托模式。- 若有特殊需求需要自定义类加载器,可以通过继承
ClassLoader
类复写它的findClass
方法实现自己的类加载器。