1. 前言
ClassLoader翻译过来就是类加载器。
阅读过JVM和Android的Dalvik与ART我们知道,Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载可以从class文件中读取,也可以是其他形式的二进制流。因此,我们常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。
只不过Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份,如果我们的Android应用不进行分dex处理,最后一个应用的apk只会有一个dex文件。
那么在理解Android的ClassLoader之前要先了解Java的ClassLoader。
2. Java的ClassLoader
大家都知道,一个Java程序编译之后,是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常。而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
2.1 Java语言系统自带有三个类加载器
– Bootstrap ClassLoader:
最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。
它是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用,JVM启动时通过Bootstrap类加载器加载rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加载。
Bootstrap没有父加载器,但是它却可以作用一个ClassLoader的父加载器,比如ExtClassLoader。
– Extention ClassLoader:
扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
– Appclass Loader也称为SystemAppClass:
加载当前应用的classpath的所有类。
这三个类加载器的加载顺去是:
1. Bootstrap CLassloder
2. Extention ClassLoader
3. AppClassLoader
2.2 双亲委托模型
“双亲委拖”是指子类加载器如果没有加载过该目标类,就先委托父类加载器加载该目标类,只有在父类加载器找不到字节码文件的情况下才从自己的类路径中查找并装载目标类。
先来看一下ClassLoader的体系架构
加载过程表述:
源ClassLoader先判断该Class是否已加载,如果已加载,则返回Class对象;如果没有则委托给父类加载器。
父类加载器判断是否加载过该Class,如果已加载,则返回Class对象;如果没有则委托给祖父类加载器。
依此类推,直到始祖类加载器(引用类加载器)。
始祖类加载器判断是否加载过该Class,如果已加载,则返回Class对象;如果没有则尝试从其对应的类路径下寻找class字节码文件并载入。如果载入成功,则返回Class对象;如果载入失败,则委托给始祖类加载器的子类加载器。
始祖类加载器的子类加载器尝试从其对应的类路径下寻找class字节码文件并载入。如果载入成功,则返回Class对象;如果载入失败,则委托给始祖类加载器的孙类加载器。
依此类推,直到源ClassLoader。
源ClassLoader尝试从其对应的类路径下寻找class字节码文件并载入。如果载入成功,则返回Class对象;如果载入失败,源ClassLoader不会再委托其子类加载器,而是抛出异常。
总体概括成两句话:
自底向上检查类是否加载
自顶向下尝试加载类
“双亲委派”机制只是Java推荐的机制,并不是强制的机制。
我们可以继承java.lang.ClassLoader类,实现自己的类加载器。如果想保持双亲委派模型,就应该重写findClass(name)方法;如果想破坏双亲委派模型,可以重写loadClass(name)方法。
再来了解一下源码:
public abstract class ClassLoader {
private ClassLoader parent;
...
public Class> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false);
}
protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
protected Class> findClass(String className) throws ClassNotFoundException {
throw new ClassNotFoundException(className);
}
...
}
2.3 Java为什么要要采用双亲委托模型
理解这个问题,我们引入另外一个关于Classloader的概念命名空间
它是指要确定某一个类,需要类的全限定名以及加载此类的ClassLoader来共同确定。
也就是说即使两个类的全限定名是相同的,但是因为不同的ClassLoader加载了此类,那么在JVM中它是不同的类。
明白了命名空间以后,我们再来看看委托模型。
采用了委托模型以后加大了不同的 ClassLoader的交互能力,比如上面说的,我们JDK本生提供的类库,比如has