android 基础-Dalvik ,ART,JIT,AOT,D
Dalvik 和 ART
Dalvik:Dalvik 虚拟机,android 5.0 以前所使用的虚拟机,可执行文件为 dex 格式,基于寄存器的虚拟机(jvm 基于堆栈)。通过 dx 工具将 .class 文件转换为 .dex 格式。
ART:android rutime,android 5.0 以后使用的虚拟机。
关于这个虚拟机的点,希望了解的关注点在于虚拟机这块。
ios 和 android对比,android 是基于虚拟机的,android 开发者使用 kotlin/java 编写代码(java 是编译-解释性语言),最终会编译成 .class 文件,.class 文件经过 dx 工具会优化成 dex 格式文件,dex 格式文件可以在虚拟机上执行。ios 开发者通过编写 Objective-C 或者 Swift 语言,是直接编译为机器码的。基于这点,ios 原生要比 android 存在优势。
但是随着编译技术的发展,两者的性能差距越来越小,甚至基本持平。
Dex 文件
java 代码通过 java 编译器生产 .class 文件,.class 文件在通过 dx 工具,生产 dex 文件。dex 格式的详细说明如链接:dex 文件格式。
JIT
JIT:Just In Time Compiler 及时编译技术。关注这个及时编译的含义。android 系统上, dex 文件 并不直接在系统层面执行的, davlik 负责解释 dex,并且生成操作系统可以执行的微指令。
jit 存在的缺陷:
- 每次启动都需要重新编译
- 运行时耗电量大
AOT
JIT 是即时编译,是动态编译,可以对执行次数频繁的 dex 代码进行编译和优化,减少使用时的编译时间,虽然可以提高执行性能,但是本身编译需要耗费时间。Google 在 5.0 之后使用 ART 替代 Dalvik,包括了 AOT 的使用。
AOT 是静态编译。应用安装过程中,会使用 dex2oat 工具,把 dex 预编译成 ELF 文件,每次运行过程不用重新编译。
android 编译过程的发展
android 2.2
早期的android 支持 jit 编译,通过 JIT 优化编译。但是显然存在问题
- 每次启动之后,都要进行 JIT 编译
- 如果执行没有进行 JIT 编译的代码,还是得不到优化效果
具体的 JIT 的流程,参考 android 7.0 的说明。
android 5.0
5.0 以后,google 使用了 新的 ART 虚拟机,在 ART 虚拟机上,使用 AOT 把字节码dex/odex 优化成可执行的字节码 ELF 文件。可是这样也会带来新的问题。
- 每次 app 升级,都要重新进行 OAT 操作
- 每次系统升级 也要进行 OAT 操作
- OAT 操作需要额外的存储空间
android 7.0
android 7.0 之后,google 醒悟了,使用了AOT + JIT 的混合编译模式;具体策略如下:
- app 首次安装,dex 不会通过 AOT 优化
- app 运行期间,dex 文件通过解析器被直接执行,热点函数会被识别,并且被 JIT 编译后存在 JIT Code Cache,并且生成 profile 文件以记录热点函数的信息
- 手机进入空闲状态,系统会扫描 appp 目录下的 profile 文件, 并且执行 AOT 过程
这样就能够避免了首次安装的等待,同时进行代码优化。
具体的 profile 文件的逻辑,参考我后面的文章。
ART 包括一个编译器工具(dex2oat工具),dex2oat 会根据 dex 文件生成一个或者多个编译工具文件,具体的可能有不同版本差异,在 android O版本下,有以下文件:
- .vdex :包含了 APK 未压缩的 DEX 代码,以及一些加快验证速度的元数据
- .odex:包括了经过 AOT 编译优化的代码
- .art:包含了 APK 中累出字符串和类的 ART 内部表示,用于加快启动速度
ART 进行编译结构如下图

- 用户运行应用,此举随后触发 ART 加载
.dex
文件。- 如果有
.oat
文件(即.dex
文件的 AOT 二进制文件),ART 会直接使用该文件。虽然.oat
文件会定期生成,但文件中不一定会包含经过编译的代码(即 AOT 二进制文件)。 - 如果
.oat
文件不含经过编译的代码,ART 会通过 JIT 和解释器执行.dex
文件。
- 如果有
- 针对任何未根据
speed
编译过滤器编译的应用启用 JIT(也就是说,要尽可能多地编译应用中的代码)。 - 将 JIT 配置文件数据转储到只有该应用可以访问的系统目录下的文件中。
- AOT 编译 (
dex2oat
) 守护程序通过解析该文件来推进其编译。
android 8.0
android 8.0 改进优化解释器速度。
android 9.0
android 9.0 之后,改进模板代码,简单来说,就是同样的代码,不同的编译模板生产的机器码不一样,android 在 9.0 优化了 vm 模板。
但是 vm 模板没有针对 不同 app 进行优化。
Dex2oat
Dex2oat 是 ART 里面的一个编译工具;
ART 的编译选项,分为两种类型:
- 系统 ROM 配置:编译系统映像时,会对哪些代码进行 AOT 编译
- 运行时配置:ART 如何在设备上编译和允许应用
编译配置,也就是传递给 dex2oat 存在四个参数:
- verify:只运行 DEX 代码验证
- quicken:运行 DEX 代码验证,并优化一些 DEX 指令,以获得更好的编译器性能
- speed: 运行 Dex 代码验证,并对所有的方法进行 AOT 编译。
- speed-profile:运行 DEX 代码验证,并对配置文件列出的方法进行 AOT 编译。
有许多 ART 构建选项可用于配置系统 ROM。如何配置这些选项取决于 /system
的可用存储空间以及预安装应用的数量。编译到系统 ROM 中的 JAR/APK 可以分为以下四个类别:
- 启动类路径代码:默认使用 speed 编译过滤器进行编译。
- 系统服务器代码:默认使用 speed 编译过滤器进行编译。
- 产品专属的核心应用:默认使用 speed 编译过滤器进行编译。
- 所有其他应用:默认使用 quicken 编译过滤器进行编译。
怎么验证手机上使用的编译模式?先 根据
adb shell
进入 adb shell ,再执行命令:
getprop | grep pm
getprop 是读取配置文件的意思,grep 是过滤选项,pm 是 package manager 的意思,最终如下:
PD1818:/ $ getprop | grep pm
[dalvik.vm.heapmaxfree]: [8m]
[dalvik.vm.heapminfree]: [4m]
[dev.pm.dyn_samplingrate]: [1]
[init.svc.dpmQmiMgr]: [running]
[init.svc.dpmd]: [running]
[persist.vendor.dpm.feature]: [11]
[persist.vendor.dpm.tcm]: [2]
[persist.vendor.radio.apm_sim_not_pwdn]: [1]
[pm.dexopt.ab-ota]: [speed-profile]
[pm.dexopt.bg-dexopt]: [speed-profile]
[pm.dexopt.boot]: [verify]
[pm.dexopt.first-boot]: [quicken]
[pm.dexopt.inactive]: [verify]
[pm.dexopt.install]: [quicken]
[pm.dexopt.shared]: [speed]
例如 pm.dexopt.first-boot 这个选项是 quicken,也是是这个手机对第一次启动的 app 采用 quick 模式编译,只验证 dex ,以及优化一些 dex 指令。pm.dexopt.bg-dexopt 模式下,则采用 speed-profile 模式,会根据配置文件进行 AOT 编译。
DexLayout
DexLayout 是 android 8.0 中引入的一个库,用于分析 dex 文件,并且根据配置文件进行重新排序。
默认的 apk 文件,等于 zip 包,zip 压缩工具默认按照文件名对文件进行排列。 Dexlayout 在app 运行期间会收集配置文件信息,然后在设备空闲的时候,把经常一起访问的文件集中在一起,对 dex 文件进行重新排序,可以改变文件位置,从而优化内存访问模式,进而节省 RAM 并且缩短启动时间。
综述
了解那么多一些底层的东西,那么实际涉及到的技术点是怎么样的?包括
- 热修复,类似 tinker 就是直接修改 dex
- 启动优化,除了优化业务代码, 包括 face book redex 或者 google 官方提供 pgo 操作,也是通过编译层面去优化启动速度的。
app 启动,是一个特别长久复杂的事情,需要了解各方面的知识才能把这个事情做得彻底。
参考资料:
Android Runtime (ART) 和 Dalvik