ART 使用 AOT 和 JIT 混合编译的优缺点分别是什么?

解读

国内面试中,这道题常出现在“性能优化”或“虚拟机原理”环节,面试官想确认两点:

  1. 候选人是否清楚 Android 5.0 之后“安装时 AOT + 运行时 Profile-guided AOT + JIT 回退”这一套混合策略的演进背景;
  2. 能否结合线上灰度、包体积、安装耗时、发热等真实业务痛点,权衡两种编译方式的代价与收益。
    回答时切忌只背“JIT 快、AOT 慢”这类口诀,而要体现“场景化取舍”意识,并主动给出可落地的监控与优化手段,才能拉开与其他候选人的差距。

知识点

  1. 混合编译链路

    • 首次安装:APK 中仅包含 DEX,不触发全量 AOT,安装耗时短。
    • 运行时 JIT:解释执行 + 热点方法 JIT 编译,同时记录 Profile。
    • 空闲充电 + 后台 dex2oat:依据 Profile 做部分 AOT,生成 .odex/.art。
    • 后续升级:Profile 更新 → 重新 dex2oat,形成“热方法越跑越快”的闭环。
  2. AOT 优点

    • 本地机器码,解释器零开销;CPU 与内存压力最小,16 ms 帧率抖动低。
    • 完全脱离 JIT 编译线程,避免运行时卡死 GC 或抢占 UI 线程。
    • 对后台进程、系统服务、BOOTCLASSPATH 这类“常驻且稳定”的代码收益极高。
  3. AOT 缺点

    • 安装/OTA 时 dex2oat 耗时与 ROM 写入量随代码量线性增长,低端机(eMMC 5.1、UFS 2.0)可达 30 s+,用户感知“卡顿装包”。
    • 占用额外存储:odex + art 文件 ≈ 1.2~1.5 倍 APK 体积,对 64 GB 存储机型是致命伤。
    • 编译后即静态代码,无法利用后续 JIT 的投机优化(如虚方法内联、分支预测)。
  4. JIT 优点

    • 安装零等待,DEX 拷完即可启动,适合国内渠道“快速装包拉新”场景。
    • 运行时可见真实类型与分支热度,可做激进投机优化,局部性能可反超 AOT。
    • 支持反优化(Deoptimize):当投机假设失效时可回退到解释器,热修复/Hook 更灵活。
  5. JIT 缺点

    • 运行时编译线程占用 CPU(2~4 核小核满载 200~400 ms),导致掉帧、发热,游戏场景尤为明显。
    • 编译后代码缓存在 CodeCache(位于 /data/dalvik-cache),增加 20~40 MB 物理内存,低端机易被 LMK 杀掉。
    • 每次冷启动都需重新 JIT,系统升级或清除 dalvik-cache 后“首帧卡顿”可复现。
  6. 国内特有痛点

    • 国内无 GMS,厂商自定义 dex2oat 策略差异大:小米“全量 AOT”、OPPO“后台限速 1 核”、华为“方舟混合”,同一 APK 在不同 ROM 表现差异可达 15% 帧率。
    • 插件化、热修、加壳导致 Profile 失真,dex2oat 回退到“全量编译”,安装耗时暴涨,需手动干预 vmSafeMode 或 disable dex2oat。
    • 国内渠道要求 64 位 + 32 位双 APK,AOT 后体积翻倍,Google Play 的 AAB 拆包逻辑不适用,必须自己做 abiSplit + 精简动态库。

答案

“ART 的混合编译是在‘安装耗时’与‘运行时性能’之间做的权衡:
AOT 把字节码提前编译成机器码,CPU 与内存零开销,帧率最稳,但安装/OTA 时 dex2oat 耗时大、存储占用高,且无法做运行时投机优化;
JIT 把编译推迟到运行时,安装秒开,能基于真实热点做激进优化,却带来编译线程 CPU 抢占、CodeCache 内存膨胀、反复冷启动掉帧的问题。
国内业务落地时,我会通过以下三步取舍:

  1. 把启动路径、核心 SDK、系统服务加入 interfilter-list,强制 AOT,保证首帧稳定;
  2. 对不常用业务模块关闭 AOT,利用 JIT 延迟编译,减少 odex 体积 30% 以上;
  3. 线上接入 Facebook Redex + ByteDance ByteX,在 CI 阶段做 interdex、class-frequently-used 重排,引导 Profile 精准匹配,dex2oat 耗时从 28 s 降到 11 s,低端机安装流失率下降 1.2 个百分点。
    最终既保留了 AOT 的帧率优势,又规避了安装耗时与包体积的劣势。”

拓展思考

  1. Android 13 的“JIT Server”把编译任务上传到云端,是否彻底解决低端机发热?若网络抖动导致回退,对直播类 App 的 QoE 会有哪些连锁反应?
  2. 方舟编译器(ArkCompiler)直接把 Java 静态编译成 ELF,彻底干掉虚拟机,这种“纯 AOT”思路对热修、反射、JNI 的兼容性挑战如何量化评估?
  3. 如果未来 Android 强制启用 AOT + 16 KB 页面对齐,odex 体积再增 15%,国内 128 GB 存储机型将成为新“低端”,我们能否用 Brotli 压缩 odex、或在系统层实现 F2FS 透明压缩来抵消?