Android Runtime (ART) — 在 Android 5.0 Lollipop 中引入的 Android 应用运行环境,用于替代 Dalvik。主要创新 — 在应用安装时直接将 DEX 字节码提前编译(AOT)为本地机器码,这消除了 JIT 编译器长期存在的预热问题。根据 Google, 2024,与 Dalvik 相比,ART 的性能提升高达 20-30%,同时保持与 DEX 格式的完全向后兼容性。
要点
Android Runtime (ART) — 在运行前将 DEX 字节码编译为本地机器码的应用运行环境。与在运行时使用即时编译(JIT)的 Dalvik 不同,ART 在 APK 安装时执行提前编译(AOT)。这一根本性的架构变化带来了显著的应用程序加速和能耗降低。
ART 首次作为实验性选项出现在 Android 4.4 KitKat 中。开发者可以在开发者设置中启用它并测试他们的应用。在 Android 5.0 Lollipop 中,ART 成为默认运行环境,Dalvik 被完全从平台中移除。到 Android 7.0 Nougat 发布时,ART 获得了混合编译模式。
用 ART 替换 Dalvik 的决定并非突然。新环境的工作始于 2012 年,当时 Google 意识到 JIT 方法的局限性。主要目标:加快应用启动速度、降低处理器负载和减少能耗。开发工作由之前从事 Dalvik 优化的 Android Runtime Group 团队领导。
ART 使用与 Dalvik 相同的寄存器架构,但编译器完全重新设计。ART 包含 AOT 编译器 dex2oat 而不是解释器和 JIT 编译器,它在安装时将 DEX 文件转换为 ELF 二进制文件。结果,ART 上的应用无需预热阶段,立即以本地性能启动。
ART 保留了 Dalvik 的关键原则:通过独立进程隔离应用、寄存器架构和对 DEX 格式的支持。然而,内部实现被完全重写。ART 包含三种执行模式:解释器、JIT 编译器和 AOT 编译器 dex2oat,取代了 Dalvik 解释器。模式的选择取决于应用的生命周期阶段。
ART 的关键组件 — dex2oat(dalvik executable to optimized android translator)。此工具在应用安装时启动(从 Android 7.0 起 — 也在后台优化时启动)。dex2oat 从 APK 读取 DEX 文件,优化字节码并生成 OAT 文件 — 包含本地代码的 ELF 二进制文件。OAT 文件存储在 /data/dalvik-cache/ 目录中。
# 检查设备上的 OAT 文件
adb shell ls -la /data/dalvik-cache/arm64/
# 强制重新编译应用
adb shell cmd package compile -m speed com.example.app
ART 系统由多个相互关联的模块组成。dex2oat 编译器负责生成本地代码。垃圾回收器(GC)管理内存释放。解释器执行很少调用的代码而无需编译。性能分析器跟踪热方法以进行混合编译。每个模块可以独立工作,这使得 ART 灵活且可扩展。
从 Android 7.0 Nougat 开始,ART 采用混合方法进行编译,结合了 JIT 和 AOT 的优势。在应用安装时,ART 不再执行完整的 AOT 编译 — 相反,应用在解释模式下运行,对热方法进行 JIT 编译。这减少了安装时间和占用的空间。
同时工作的还有后台性能分析器(background profiler)。它收集执行统计信息:哪些方法最常被调用、哪些代码分支被执行、哪些类被加载。在收集足够的数据后(通常经过 2-3 次应用启动),ART 在后台运行 dex2oat 并仅将经过性能分析的热方法编译为本地代码。
ART 支持多种编译模式,通过 system_server 管理。"speed" 模式将所有方法编译为 AOT(最高性能,安装时间长)。"speed-profile" 模式仅编译经过性能分析的热方法(速度和大小平衡)。"verify" 模式仅验证字节码而不编译(最小空间,解释执行)。默认使用 speed-profile — 对大多数应用最优。
| 模式 | 编译方式 | 安装时间 | 性能 |
|---|---|---|---|
| speed | 完全 AOT | 长 | 最高 |
| speed-profile | 性能分析后 AOT | 快 | 高 |
| verify | 无编译 | 即时 | 解释执行 |
| space | 最小 AOT | 中等 | 中等 |
性能分析器将执行数据收集到专门的 .prof 文件中。每个应用将其配置文件存储在 /data/misc/profiles/ 中。达到阈值后(通常为 1000 个样本),性能分析器运行 dex2oat 以编译识别到的热方法。配置文件在应用更新之间保留,这加速了系统 OTA 更新后的重新优化。
ART 中的垃圾回收与 Dalvik 相比有了根本性的改进。ART 使用带多项优化的分代收集器取代了单线程的 Concurrent Mark and Sweep (CMS):移动收集器(压缩堆)、大对象空间(单独存储大对象)和并发压缩(并行压缩)。
ART 中典型的 GC 暂停为 2-3 毫秒,而 Dalvik 为 5-10 毫秒。这得益于多种机制。首先,ART 在并发阶段使用读屏障(read-barrier)而不是停止世界(stop-the-world)。其次,分代收集器在大多数周期中仅处理年轻代对象,而不影响整个堆。第三,大对象空间(LOS)单独分配,不参与常规 GC 周期。
// 启用 GC 日志进行调试
System.logV("ART", "GC trigger: allocation failed");
// 强制调用 GC(不建议在生产环境中使用)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Debug.getRuntimeIStats();
}
尽管 GC 得到改进,内存泄漏仍然是一个现实问题。ART 特有的原因 — 通过 JNI 加载本地库而未正确释放。如果本地代码通过 malloc 分配内存但不调用 free,ART 无法释放此内存 — 它位于托管堆之外。Android NDK 中的 AddressSanitizer 工具有助于检测此类泄漏。
ART 和 Dalvik — 同一任务(运行 Android 应用)的两种根本不同的实现。差异影响所有层面:从编译到内存管理。以下是关键性能和兼容性参数的比较。
ART 的主要优势 — 消除 JIT 预热。在 Dalvik 上,应用在前 3-10 秒可能变慢,因为 JIT 正在编译热方法。在 ART 上,所有方法都已编译为本地代码(或将在后台编译)。这在游戏和具有复杂 UI 的应用中尤为明显:帧率差异可能达到 15-20%,有利于 ART。
| 参数 | Dalvik | ART |
|---|---|---|
| 编译方式 | JIT(运行时) | AOT + 混合(安装时) |
| 启动时间 | 3-10 秒(预热) | 即时 |
| APK 大小 | 约 6-7 MB(DEX) | +20%(OAT) |
| GC 暂停 | 5-10 毫秒 | 2-3 毫秒 |
| 能耗 | 较高(JIT 使 CPU 发热) | 较低(本地代码) |
所有为 Dalvik 编写的应用在 ART 上无需修改即可运行。Google 保证在 DEX 字节码级别的完全向后兼容性。例外 — 通过反射使用 Dalvik 特定内部 API 的代码:Android SDK 中标记为 @hide 的 dalvik.system.DexFile 类成员。此类代码应更新为使用公共 API。
ART 成为第一个原生支持 Java 8 功能的 Android 运行环境。从 Android 7.0 开始,ART 包含脱糖处理(desugaring)— 将 Java 8 构造(lambda 表达式、方法引用、Stream API)转换为等效 Java 7 代码的过程。这允许使用现代语法而不失去对旧设备的兼容性。
脱糖处理由 D8 编译器执行,工作方式如下。包含 lambda 的源代码转换为同一类中的合成方法,lambda 被替换为 invoke-custom 调用。ART 运行环境支持专门为 Java 8 添加的 invoke-custom 指令。在 Android 6.0 及更低版本的设备上,lambda 被脱糖为匿名类。
// Java 8 lambda — ART 中的脱糖处理
button.setOnClickListener(v -> handleClick(v));
// 脱糖处理后(Java 7 中的等效代码)
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleClick(v);
}
});
并非所有 Java 8 功能都受脱糖处理支持。java.time API(日期和时间)只能通过 desugar_jdk_libs 访问 — 一个添加到 build.gradle 中的额外库。Stream API 也需要 desugar_jdk_libs。java.util.function 和 Optional 无需额外依赖即可工作。Android 8.0 及更高版本的设备上无需脱糖处理即可获得完整的 Java 8 支持。
尽管 ART 向后兼容,但某些优化实践正是针对此环境提高性能。主要建议 — 最小化反射。ART 将编译阶段可见的方法编译为直接机器码调用。反射迫使 ART 生成额外的存根(stub),这会降低 10-15% 的执行速度。
从 Android 9.0 开始,ART 支持 App Startup Optimization。开发者可以通过 <initialization> 在清单中标记初始化类,ART 将预先加载它们。对于拥有大量插件或库的应用,这可以将启动时间减少 5-15%。
<!-- AndroidManifest.xml 中的 App Startup Optimization -->
<application>
<profileable
android:shell="true"
android:enable="true" />
</application>
要在 ART 上测量性能,请使用 systrace 和 perfetto。Systrace 显示 dex2oat 编译时间、GC 频率和帧渲染速度。Perfetto 提供更详细的信息:线程分布、JNI 转换时间、本地库加载。启动:adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto -t 10s sched freq idle am wm。
常见问题
ART(Android Runtime)— 在安装时将应用代码编译为机器码的 Android 应用运行环境。与旧的 Dalvik 环境相比,这加快了应用的启动和运行速度。
ART 在应用安装时提前(AOT)编译代码,而 Dalvik 在运行时逐步(JIT)编译。因此,在 ART 上应用启动更快且能耗更低。
执行 adb shell getprop 并查找属性 persist.sys.dalvik.vm.lib.2。值 "libart.so" 表示 ART,"libdvm.so" — Dalvik。所有 Android 5.0+ 的设备上,运行环境都是 ART。
影响很小。应用本身以 APK 格式保持 DEX 文件不变。ART 在 /data/dalvik-cache/ 中创建额外的 OAT 文件,占用空间比原始 DEX 多 10-20%,但此存储不计入 APK 大小。
是的,ART 通过脱糖处理机制支持大部分 Java 8 功能。Lambda 表达式、方法引用和函数式接口可在所有 Android 5.0+ 设备上运行。Stream API 和 java.time 需要 desugar_jdk_libs 库。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。