Android类加载机制
一.虚拟机
Android和Java都运行在虚拟机上,java运行虚拟机叫JVM, Android则是Dalvik(5.0之前)和ART(5.0及之后)虚拟机。
二.Android虚拟机
Dalvik虚拟机是2008年跟随Android系统一起发布的。当时的移动设备的系统内存只有64M左右,CPU频率在250~500MHz之间。这个硬件水平早已发生了巨大变化。随着智能设备的兴起,这些年移动芯片的性能每年都有大幅提升。如今的智能手机内存已经有6G甚至8G至多。CPU也已经步入了64位的时代,频率高达2.0 GHz甚至更高。硬件的更新,常常也伴随着软件的换代。因此,Dalvik虚拟机被淘汰也是情理之中的事情。
Dalvik虚拟机缺点
Dalvik之所以要被ART替代包含下面几个原因:
- Dalvik是为32位设计的,不适用于64位CPU。
- 单纯的字节码解释加JIT编译的执行方式,性能要弱于本地机器码的执行。
- 无论是解释执行还是JIT编译都是单次运行过程中发生,每运行一次都可能需要重新做这些工作,这样做太浪费资源。
- 原先的垃圾回收机制不够好,会导致卡顿。
ART的优势
很显然,ART虚拟机对上面提到的这些地方做了改进。除了支持64位不必说,最主要的是下面两项改进:
- AOT编译:Ahead-of-time(AOT)是相对于Just-in-time(JIT)而言的。JIT是在运行时进行字节码到本地机器码的编译,这也是为什么Java普遍被认为效率比C++差的原因。无论是解释器的解释还是运行过程中即时编译,都比C++编译出的本地机器码执行多了一个耗费时间的过程。而AOT就是向C++编译过程靠拢的一项技术:当APK在安装的时候,系统会通过一个名称为dex2oat的工具将APK中的dex文件编译成包含本地机器码的oat文件存放下来。这样做之后,在程序执行的时候,就可以直接使用已经编译好的机器码以加快效率。
- 垃圾回收的改进:GC(Garbage Collection)是虚拟机非常重要的一个特性,因为它的实现好坏会影响所有在虚拟机上运行的应用。GC实现得不好可能会导致画面跳跃,掉帧,UI响应过慢等问题。
- ART的垃圾回收机制相较于Dalvik虚拟机有如下改进:
3.1 将GC的停顿由2次改成1次 (Dalvik需要先标记,再清除,而在ART中,标记过程是并发进行的,它让线程标记自己的根,然后马上就恢复运行)
3.2 在仅有一次的GC停顿中进行并行处理
3.3 在特殊场景下,对于近期创建的具有较短生命的对象消耗更少的时间进行垃圾回收
3.4 改进垃圾收集的工效,更频繁的执行并行垃圾收集
3.5 对于后台进程的内存在垃圾回收过程进行压缩以解决碎片化的问题
- Android类加载器
Android中的类加载器有三种,DexClassLoader、PathClassLoader、BootClassLoader。其中BootClassLoader是系统启动时预加载常用类的,一般使用不到。DexClassLoader、PathClassLoader都是继承自BaseDexClassLoader;但DexClassLoader和PathClassLoader并没有重写BaseDexClassLoader中的任何方法。
通过源码追踪可以看到,Android类加载链路:
--> PathClassLoader.findClass
--> BaseDexClassLoader.findClass
--> BaseDexClassLoader.pathList.findClass
--> DexPathList.dexElements.foeach{element.findClass}
--> Element.findClass
--> Element.dexFile.loadClassBinaryName()
--> DexFile.defineClass()
可以看到最终到了DexFile.defineClass方法,然后交给Native层处理了。
双亲委派机制
双亲委派机制发生在类加载过程的开始,当需要加载一个类时,会优先去父加载器中递归查看该类是否被加载过,一直递归查找到ClassLoader类;若加载过,则直接返回该类对象,若都没有加载过,则就自顶层的ClassLoader去开始依次向下查找,每个加载器会从自己规定的位置去查找这个类,查找到即进入加载过程;若都为查找到,则交由发起者CustomClassLoader加载该类;
详细逻辑如ClassLoader的loaderClass方法:
双亲委派机制的好处
- 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是先从缓存中直接读取。
- 更加安全,因为虚拟机认为只有两个类名一致并且被同一个类加载器加载的类才是同一个类,所以这种机制保证了系统定义的类不会被替代。
热修复
类加载过程中,可以看到这一步:
这里的dexElements即是DexPathList类中的一个数组,官方解释如下:
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private final Element[] dexElements;
这个dexElements即表示dex/resource的集合,其查找支持熔断,即找到第一个符合条件的,边不会再往后查找。热修复也正式利用了这一点,将修复后的class打包成新的hotfixDex.dex,插入到dexElements的前面,那么加载类时,会优先加载hotfixDex.dex里面的被修复过的类,数组后面有bug的类将不再被加载。