一、Java中的类加载器
我们知道.class文件存储着Java程序代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件
并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。
首先我们来了解下Java中的类加载器,Java中的类加载器主要有两种类型
系统类加载和自定义类加载器
1. 系统类加载:
Bootstrap ClassLoader 启动类加载器
C++语言实现的;是虚拟机自身的一部分,因此它并不继承java.lang.ClassLoader
加载的是JVM自身需要的类;如java.lang.、java.uti.等这些系统类;
将/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中
出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
Extensions ClassLoader 扩展类加载器
sun.misc.Launcher$ExtClassLoader类,继承与java.lang.ClassLoader类
用于加载 Java 的拓展类
拓展类的jar包一般会放在$JAVA_HOME/jre/lib/ext目录下;也可以通过-Djava.ext.dirs选项添加和修改Extensions ClassLoader加载的路径
App ClassLoader
sun.misc.Launcher$AppClassLoader,继承与java.lang.ClassLoader类
负责加载当前应用程序Classpath目录下的所有jar和Class文件也可以加载通过-Djava.class.path选项所指定的目录下的jar和Class文件,即所谓的Classpath
2. 自定义类加载器
除了系统提供的类加载器,还可以自定义类加载器,自定义类加载器通过继承java.lang.ClassLoader类的方式来实现自己的类加载器
自定义加载器用于加载网络上的或者某个文件夹中的jar包和Class文件
实现自定义ClassLoader步骤
1. 定义一个自定义ClassLoader并继承ClassLoader
2. 复写findClass方法,并在findClass方法中调用defineClass方法
二、类加载器查找类的过程–双亲委托模式
我们知道ClassLoader都是通过其中的loadClass方法来加载类的,那么我们先看下ClassLoader的loadClass源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
//判断是否已经加载,如果已经加载则直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
//判断当前加载器是否有父加载器,有的话就调用父加载器的loadClass方法
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//没有父加载器,调用BootstrapClassLoader进行加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果仍然无法加载成功,则调用自身的findClass进行加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
在加载器有一个parent父加载器的概念,注意父加载是创建加载器的时候指定的,并不是继承关系。在java中
App ClassLoader的父加载器 是Extensions ClassLoader
Extensions ClassLoader的父加载器 是Bootstrap ClassLoader
而Bootstrap ClassLoader是根加载器,没有父加载器了
如果我们自定义一个加载器,父加载器指定为App ClassLoader,那么我们发现类加载的过程就是
- 判断当前类是否已经加载,如果已经加载则直接返回,避免重复加载
- 如果未加载,则递归遍历调用其父加载器的loadClass方法,父加载器的loadClass方法也是重复1和2这两步,如果加载成功则返回,如果没加载成功,则继续向上遍历,直到没有没有了父加载器,则调用BootstrapClass的loadClass方法
- 如果遍历完后还没有加载成功,则调用加载器自己的findClass方法
这就是所谓的双亲委托模式。双亲委托模式主要有两点好处:
1. 避免重复加载,如果已经加载过一次Class,则再次加载的时候会从缓存中读取
2. 更加安全,可以避免自定义一个String类来替代系统的String类这种情况发生。注意的是: 只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为它们是同一个类
三、Android中的类加载器
我们知道Android中对.class文件进行了合并优化,生成了.class.dex文件,所以在Android中的类加载,其实就是加载.dex文件
在Android中主要定义了两个ClassLoader来进行类加载
即DexClassLoader和PathClassLoader,他们都继承与BaseDexClassLoader,而BaseDexClassLoader又继承与ClassLoader
PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,其中的主要逻辑都是在BaseDexClassLoader完成的
我们下面来看下这三者的构造器的构造函数
BaseDexClassLoader
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
参数详解:
1. dexPath:JAR/APK文件路径的列表,多个的话默认可用“:”分割;可以是APK、DEX和JAR的路径
2. optimizedDirectory:该参指定从jar或者Apk中解压出的dex 文件存放的路径。这也是对apk中dex根据平台进行ODEX优化的过程
APK是一个程序压缩包,里面包含dex文件,ODEX优化就是把包里面的执行程序提取出来,就变成ODEX文件
系统只有第一次启动的时候,会把JAR或者APK解压到 /data/dalvik-cache(针对PathClassLoader)或者optimizedDirectory(针对DexClassLoader)目录,之后也是直接读取目录下的的dex文件
3. librarySearchPath:指目标类中所使用的C/C++库存放的路径
4. parent:父加载器
DexClassLoader
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
DexClassLoader支持加载APK、DEX和JAR,也可以从SD卡进行加载。在BaseDexClassLoader里对”.jar”,”.zip”,”.apk”,”.dex”后缀的文件最后都会生成一个对应的dex文件,所以最终处理的还是dex文件,生成的dex文件就放在optimizedDirectory目录下
PathClassLoader
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
可以看出PathClassLoader将optimizedDirectory置为Null,也就是没设置优化后的存放路径。其实optimizedDirectory为null时的默认路径就是/data/dalvik-cache 目录。
PathClassLoader是用来加载Android系统类和应用的类
ClassLoader加载class的过程
//BaseDexClassLoader
@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
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
可以看出加载的过程就是
BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的集合dexElements。类加载,就是遍历这个集合,通过DexFile去寻找。
其实本质就是:一个ClassLoader可以加载多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。
33万+

被折叠的 条评论
为什么被折叠?



