一、ART 即时 (JIT) 编译器实现
Android Runtime (ART) 包含一个具备代码分析功能的即时 (JIT) 编译器,该编译器可以在 Android 应用运行时持续提高其性能。JIT 编译器对 Android 运行组件当前的预先 (AOT) 编译器进行了补充,可以提升运行时性能,节省存储空间,加快应用和系统更新速度。相较于 AOT 编译器,JIT 编译器的优势也更为明显,因为在应用自动更新期间或在无线下载 (OTA) 更新期间重新编译应用时,它不会拖慢系统速度。
尽管 JIT 和 AOT 使用相同的编译器,它们所进行的一系列优化也较为相似,但它们生成的代码可能会有所不同。JIT 会利用运行时类型信息,可以更高效地进行内联,并可让堆栈替换 (OSR) 编译成为可能,而这一切都会使其生成的代码略有不同。
- 1、OAT文件可以直接运行
- 2、非OAT文件需要解释运行
- 3、如果是代码达到热点的方法的阈值,会让JIT在后台编译,编译之后添加到缓存中
- 4、JIT Code Cache 中的代码等到下次运行时如果命中缓存,直接使用缓存代码执行(Code Cache是内存缓存)
JIT 编译涉及以下活动
- 用户运行应用,此举随后触发 ART 加载
.dex
文件。- 如果有
.oat
文件(即.dex
文件的 AOT 二进制文件),ART 会直接使用该文件。虽然.oat
文件会定期生成,但文件中不一定会包含经过编译的代码(即 AOT 二进制文件)。 - 如果
.oat
文件不含经过编译的代码,ART 会通过 JIT 和解释器执行.dex
文件。
- 如果有
- 针对任何未根据
speed
编译过滤器编译的应用启用 JIT(也就是说,要尽可能多地编译应用中的代码)。 - 将 JIT 配置文件数据转储到只有该应用可以访问的系统目录下的文件中。
- AOT 编译 (
dex2oat
) 守护程序通过解析该文件来推进其编译。
JIT 工作流程
(注意:JIT解释器解释完其实可以立即执行,改图未标注)
- 分析信息会存储在代码缓存中,并会在内存紧张时作为垃圾被回收。
- 无法保证在应用处于后台运行状态时所捕获的快照能够包含完整的数据(即 JIT 编译的所有内容)。
- 该过程不会尝试确保记录所有内容(因为这会影响运行时性能)。
- 方法可能有三种不同的状态:
- 已经过解释(dex 代码)
- 已经过 JIT 编译
- 已经过 AOT 编译
- 在不影响前台应用性能的情况下运行 JIT 所需的内存取决于相关应用。大型应用比小型应用需要更多内存。一般来说,大型应用所需的内存稳定维持在 4 MB 左右。
- Carbage Collect清理内存,为JIT Code Cache腾出空间
二、dex2oat编译
在 compiler_filter.h ,我们可以看到dex2oat一共有12种编译模式:
enum Filter {
VerifyNone, // Skip verification but mark all classes as verified anyway.
kVerifyAtRuntime, // Delay verication to runtime, do not compile anything.
kVerifyProfile, // Verify only the classes in the profile, compile only JNI stubs.
kInterpretOnly, // Verify everything, compile only JNI stubs.
kTime, // Compile methods, but minimize compilation time.
kSpaceProfile, // Maximize space savings based on profile.
kSpace, // Maximize space savings.
kBalanced, // Good performance return on compilation investment.
kSpeedProfile, // Maximize runtime performance based on profile.
kSpeed, // Maximize runtime performance.
kEverythingProfile, // Compile everything capable of being compiled based on profile.
kEverything, // Compile everything capable of being compiled.
};
注意:android官方上出现了quicken模式解释为:“运行 DEX 代码验证,并优化一些 DEX 指令,以获得更好的解译器性能”,因此可以推断出interpret-only和quicken相似
以上12种编译模式 按照排列次序逐渐增强 ,那系统默认采用了哪些编译模式呢?我们可以在在手机上执行 getprop | grep pm
查看:
pm.dexopt.ab-ota: [speed-profile]
pm.dexopt.bg-dexopt: [speed-profile]
pm.dexopt.boot: [verify-profile]
pm.dexopt.core-app: [speed]
pm.dexopt.first-boot: [interpret-only]
pm.dexopt.forced-dexopt: [speed]
pm.dexopt.install: [interpret-only]
pm.dexopt.nsys-library: [speed]
pm.dexopt.shared-apk: [speed]
[dalvik.vm.heapmaxfree]: [8m]
[dalvik.vm.heapminfree]: [512k]
[persist.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]: [speed-profile]
[pm.dexopt.shared]: [speed]
其中有几个我们是特别关心的,
-
install
(应用安装)与first-boot
(应用首次启动)使用的是[interpret-only],即只verify,代码解释执行即不编译任何的机器码,它的性能与Dalvik时完全一致,先让用户愉快的玩耍起来。 -
ab-ota
(系统升级)与bg-dexopt
(后台编译)使用的是[speed-profile],即只根据“热代码”的profile配置来编译。这也是N中混合编译的核心模式。 - 对于动态加载的代码,即
forced-dexopt
,它采用的是[speed]模式,即最大限度的编译机器码,它的表现与之前的AOT编译一致。
总的来说,程序使用loaddex动态加载的代码是无法享受混合编译带来的好处,我们应当尽量 采用ClassN.dex方式来符合Google的规范 。这不仅在ota还是混合编译上,都会带来很大的提升。
dex2oat支持的编译模式
<