学习笔记-Android虚拟机与类加载机制
JVM与Dalvik
Android 应用程序运行在 Dalvik/ART 虚拟机,并且每一个应用程序对应有一个单独的 Dalvik 虚拟机实例。Dalvik 虚拟机实则也算是一个 Java 虚拟机,只不过它执行的是 dex文件,而不是 class 文件。
Dalvik 虚拟机与 Java 虚拟机有差不多的特性,差别在于两者执行的指令集是不一样的,前者的指令集是基于寄存器的,而后者的指令集是基于堆栈的。

基于栈的虚拟机
对于基于栈的虚拟机来说,每一个运行的线程,都有一个独立的栈。栈中记录了方法调用的历史,每有一次方法调用,栈中便会多一个栈帧。最顶部的栈帧称作当前栈帧,其代表着当前执行的方法,基于栈的虚拟机通过操作数栈进行所有操作。

字节码指令

执行过程:

字节码指令详情:cloud.tencent.com/developer/a…
寄存器
寄存器是 CPU的组成部分。寄存器是有限存贮容量的告诉存贮部件,它们可以用来暂存指令、数据和位址。

基于寄存器的虚拟机
基于寄存器的虚拟机中没有操作数栈,但有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上就是一个数组。与 JVM 相比,在 Dalvik VM 中每个线程都有自己的 PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。
与 JVM 相比,可以发现 Dalvik 虚拟机的指令数明显减少了,数据移动次数也明显减少了。

ART 与 Dalvik
Dalvik VM 也是实现了 JVM 规范的一个虚拟机,默认使用 CMS垃圾回收器,但是与 JVM 运行 Class字节码文件不同,DVM 执行 dex (Dalvik Executable Format),专为 Dalvik 设计的一种压缩格式。dex文件是很多 .class文件处理压缩后的产物,最终可以在 Android运行时环境执行。
Dalvik 虚拟机执行的是 dex 字节码,从 Android2.2 版本开始,支持 JIT即时编译 (Just in Time),在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。
而 ART(Android RunTime) 是在Android 4.4中引入的一个开发者选项,也是 Android 5.0 以及更高版本的默认Android运行时。ART 虚拟机执行的是本地机器码。Android 的运行时从 Dalvik虚拟机替换成 ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK 仍然是一个包含 dex 字节码的文件。
ART 和 Dalvik 都是运行 dex字节码的兼容运行时,因此针对 dalvik开发的应用也能在 ART 环境中运作。
dexopt与dex2oat
Dalvik下应用在安装的过程,会执行一次优化,将dex 字节码优化生成 odex文件。 而ART下将应用的 dex字节码翻译成本地机器码的最恰当 AOT 时机也就发生在应用安装的时候。ART 引入了 AOT (预先编译机制 Ahead Of Time) ,在安装时,ART使用设备自带的 dex2oat工具来编译应用,dex中的字节码将被编译成本地机器码。
dexopt
在 Dalvik 中虚拟机在加载一个
dex文件时,对dex文件进行验证和优化的操作,其对dex文件的优化结果变成了odex((Optimized dex)文件,这个文件和dex文件很像,只是使用了一些优化操作码。dex2oat
ART预先编译机制,在安装时对dex文件执行AOT提前编译操作,编译为OAT( 实际上是 ELF 文件)可执行文件(机器码)。

Android N 的运作方式
ART 使用预先编译(AOT), 并且从Android N混合使用 AOT 编译,解释和JIT。
- 最初进行安装时不进行任何
AOT编译(安装快了),运行过程中解释执行,对经常执行的方法JIT,经过JIT编译的方法将会记录到Profile配置文件中。 - 当设备闲置和充电时,编译守护进程会运行,根据
Profile文件对常用代码进行AOT编译。待下次运行时直接使用。

ClassLoader
任何一个 Java程序都是由一个或多个 class文件组成,在程序运行时,需要将class文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java的类加载机制。 ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 class 对象的内部都有一个 classLoader对象来标识自己是由哪个 Classloader 加载的。


ClassLoader 是一个抽象类,它的具体实现类主要有:
BootClassLoader
用于加载
Android Framework层class文件PathClassLoader
用于
Android应用程序类加载器。 可以加载指定的dex,以及jar、zip、apk中的classes.dexDexClassLoader
用于加载指定的
dex,以及jar、zip、apk中的classes.dex
注:在dalvik 虚拟机上,PathClassLoader只能加载已安装apk 的 dex,但是我们现在一般不用关心 Dalvik

PathClassLoader 与 DexClassLoader 的共同父类是 BaseDexClassLoader
publicclassDexClassLoaderextendsBaseDexClassLoader{
publicDexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent){
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
publicclassPathClassLoaderextendsBaseDexClassLoader{
publicPathClassLoader(String dexPath, ClassLoader parent){
super(dexPath, null, null, parent);
}
publicPathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent){
super(dexPath, null, librarySearchPath, parent);
}
}
可以看到两者唯一的区别在于:创建 DexClassLoader 需要传递一个 optimizedDirectory 参数,并且会将其创建为 File 对象传递给 super,而 PathClassLoader 则直接给到 null。因此两者都可以加载指定的dex、以及 jar、zip、apk中的classes.dex。
PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/xx.dex", getClassLoader());File dexOutputDir = context.getCodeCacheDir();
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/xx.dex",dexOutputDir.getAbsolutePath(), null,getClassLoader());
其实 optimizedDirectory 参数就是 dexopt的产出目录(odex)。 那 PathClassLoader 创建时,这个目录为 null就意味着不进行 dexopt 吗? 并不是, optimizedDirectory 为 null 时的默认路径为 :/data/dalvik-cache。
注: 在API 26源码中,将 DexClassLoader 的 optimizedDirectory 标记为了 deprecated 弃用,实现也变为了:
publicDexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent){super(dexPath, null, librarySearchPath, parent);
}
......和 PathClassLoader 一摸一样了!
双亲委托机制
查看源码可以看到创建 ClassLoader 需要接收一个 ClassLoader parent 参数。 这个 parent的目的就在于实现类加载的双亲委托。即:
某个类加载器在接收到类加载的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成加载任务时,才自己加载。

因此,我们自己创建的
ClassLoader:new PathClassLoader("/sdcard/xx.dex", getClassLoader());并不仅仅只能加载dex中的class。
值得注意的是:c = findBootstrapClassOrNull(name); 按照方法名理解,应当是 parent 为 null 的时候,也能够加载 BootClassLoader 加载的类。
new PathClassLoader("/sdcard/xx.dex", null) 能否加载 Activity.class ???
但实际上,Android 中的实现为 :

findClass
可以看到在所以 ClassLoader 都无法加载 class 时,则会调用自己的 findClass 方法。 findClass 在 ClassLoader 中的定义为:

任何 ClassLoader 的子类都可以重写 loadClass 和 findClass 方法。
一般如果你不想使用 双亲委托,则重写 loadClass 修改其实现。
而重写 findClass则表示在双亲委托下,父 ClassLoader 都找不到类的情况下,定义自己如何去查找一个 Class。
而 我们的 PathClassLoader 会自己负责加载 MainActivity这种程序中自己编写的类,利用双亲委托父 ClassLoader加载 framework中的 Activity。 说明 PathClassLoader并没有重写 loadClass,因此,我们可以看看 PathClassLoader 中的 findClass 如何实现的:

实现非常简单,从 pathList 中查找 Class。 继续查看 DexPathList

publicDexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory, boolean isTrusted)
{//......
// splitDexPath(..) 实现为返回 List<File>.add(dexPath)
// optimizedDirectory 指定输出dex优化后的odex文件,可以为null
// makeDexElements(..) 会去 List<File>.add(dexPath) 中使用DexFile加载dex文件,包装成 Element对象,最后返回 // // Element数组
//
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);
//......
}
类加载

热修复
PathClassLoader 中存在一个 Element数组, Element 中存在一 dexFile的成员表示 dex 文件,即: APK 中有 X个 dex, Element 中就有 X个元素。

在 PathClassLoader 中的数组为:[Patch.dex, classes.dex , classes2.dex]。如果存在 Key.class 位于 patch.dex与 classes2.dex 中都存在一份,当进行类查找时,循环获得 dexElements 中的 dexFile,查找到了 Key.class 则立即返回,不会再管后续的 Element 中的 DexFile 是否能加载到 Key.class 了。
因此,一种热修复的实现跨越将出现 bug 的 Class单独制作一份 fix.dex 补丁包文件,然后在程序启动时,从服务器下载 fix.dex 保存到某个路径,再通过 fix.dex 的文件路径,用其创建 Element 对象,然后将这个 Element 对象插入到我们程序的类加载器 PathClassLoader 的 pathList 中的 dexElements 数组头部,这样在加载出现 Bug 的
class 时会优先加载 fix.dex 中的修复类,从而解决 Bug。
以上是 学习笔记-Android虚拟机与类加载机制 的全部内容, 来源链接: utcz.com/a/33445.html
