什么是类加载?
我们平时编写的.java文件不是可执行的文件,需先编译成.class文件才可以被虚拟机执行。所谓类加载是指通过类加载器把class文件加载到虚拟机的内存空间,具体来说是方法区。类通常是按需加载,即第一次使用该类时才加载。
Java与Android类加载的区别:
Java与Android都是把类加载到虚拟机内存中,然后由虚拟机转换成设备识别的机器码。但是由于二者使用的虚拟机不同,所以在类加载方面也是有所区别的。Java的虚拟机是JVM,Android的虚拟机是dalvik/art(5.0以后虚拟机是art,是对dalvik的一种升级)。Java虚拟机运行的是class文件,而Android 虚拟机运行的是dex文件。dex其实是class文件的集合,是对class文件优化的产物,是为了避免出现重复的class。
Android中最主要的5个类加载器:
ClassLoader :所有类加载器的抽象基类;
BootClassLoader :用于加载Android系统的类,是ClassLoader的内部类,开发者无法调用;
BaseDexClassLoader :继承ClassLoader;
PathClassLoader :继承于BaseDexClassLoader,通常用于加载APK中我们自己写的类(含三方库),但不局限于此;
DexClassLoader :继承于BaseDexClassLoader,通常用于执行动态加载,能够加载指定路径的apk/jar/zip/dex文件, 因此很多热修复和插件化方案都是采用;(DexClassLoader可以指定odex的路径,而PathClassLoader则采用系统默认的缓存路径,在8.0以后没有区别。)
BaseDexClassLoader小结:
1.首先在BaseDexClassLoader构造方法内创建了PathDexList对象;
2.然后在DexPathList构造方法中,通过makeDexElements()等方法经过一些列调用,把dex文件做优化再缓存到指定目录,如果是包含dex的apk/jar/zip等压缩文件的话,会先解压再优化缓存,最后得到DexFile对象;
3.将DexFile对象包装成Element对象,然后加到Element[] 数组。
类加载流程:
1.先检查这个类是否已经被加载,有的话直接返回Class对象;
2.如果没有加载过,通过父类加载器去加载,可以看出parent是通过递归的方式去加载class的;
3.如果所有的父类加载器都没有加载过,就由当前的类加载器去加载。
双亲委托机制:
1.保证class只会被加载一次,也就是说类的数据结构只会在第一次创建的时候被加载进内存(方法区),以后要创建这个类的对象的时候,直接用方法区中的class,在堆内存创建一个对象,这样的话创建对象就会比较快;
2.保证系统类的安全性。因为在启动应用进程的时候就已经加载好了系统类(BootClassLoader),那后面运行期就不可能通过恶意伪造加载的方式去造成一些系统安全问题。
小结:
1.加载一个类是通过双亲委托机制来实现的;
2.如果是第一次加载class,那是通过BaseDexClassLoader中的findClass方法实现的;接着进入DexPathList中的findClass方法,内部通过遍历Element数组,从Element对象中去查找类;Element实际上是对Dex文件的包装,最终还是从dexfile去查找的class;
3.一般app运行主要用到2个类加载器,一个是PathClassLoader:主要用于加载自己写的类;另一个是BootClassLoader:用于加载Framework中的类;
4.热修复和插件化一般是利用DexClassLoader来实现;
5.PathClassLoader和DexClassLoader其实都可以加载apk/jar/dex,区别是 DexClassLoader 可以指定 optimizedDirectory,而PathClassLoader 只能使用系统默认位置。但是在8.0 以后二者是没有区别的,只能使用系统默认的位置了。