什么是类加载器
类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件
类加载的过程可以分为以上的5步:
加载:加载指的是把class字节码文件从各个来源通过类加载器装载入内存中
验证:主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
准备:主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。
-
解析:将常量池内的符号引用替换为直接引用的过程。
符号引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
直接引用:可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量
比如说变量name="jack",这个变量的地址是1234567,那么name就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
Android中的类加载器
Android中的类加载器和Java中的不同,Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份。
Android中常用的类加载器的继承关系如下:
- BootClassLoader:启动类加载器主要加载的是JVM自身需要的类。
- BaseDexClassLoader:继承ClassLoader类,主要实现一些基本 的功能,为自定义加载器提供一个基类。
- DexClassLoader:支持加载外部的APK、Jar或dex文件,比如SD卡中的类;(所有的插件化方案都是使用它来加载插件APK中的.class文件,也是动态加载的核心依据!)
- PathClassLoader:只能加载系统中已经安装过的apk
所以Android系统默认的类加载器为PathClassLoader
类加载的双亲委托机制
所有的类加载器的基类是ClassLoader类,那么类加载器是如何去查找一个类的呢?我们直接来看ClassLoader类的loadClass方法,这个方法就是根据类名字加载类的主要方法:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 注释1
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 注释2
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//注释3
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
我们一一来看这个方法:
注释1:这里先从已加载的缓存中查找这个类是否已经加载过,如果是,则这直接返回,否则进行下一步
-
注释2:判断是否有父类加载器,如果有,则调用父类加载器去加载这个类。如果没有父类加载器,则调用findBootstrapClassOrNull方法来查找BootstrapClassLoader,这个方法的默认实现是null。
注意:这里的parent不会为空,在创建ClassLoader时,会使用BootClassLoader作为父加载器,具体可以看下面的ClassLoader的代码:
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
//构建一个PathClassLoader,parent为BootClassLoader
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
-
注释3:如果上面都没有找到,则调用当前加载器的findclass方法。我们来看看Android中默认加载器PathClassLoader类的findClass方法;
具体可以查看文章:Android类加载器
以上基本上就是双亲委托机制的原来,总结出图片就是:
双亲委托机制意义
Android/Java中之所以使用这种机制,有如下几个好处:
- 通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
- 更加的安全。比如我们自己创建了一个
java.lang.String
的类,想替换掉系统的String类。如果用这种机制,就可以防止这种,因为他会先使用系统类加载器加载,然后直接返回结果,根本就不会加载你伪造的类。
注意:从上面可以知道一个相同的class,这必须满足三个条件:
- 包名相同
- 类名相同
- 同一个类加载器加载