7、ART运行时架构:AOT编译与JIT编译,混合编译模式,ART的核心优势

聊到Android内存管理,绕不开ART运行时。我记得刚入行那会儿,Android还在用Dalvik虚拟机,每次App启动都要等它“预热”,那个慢啊……后来ART横空出世,整个体验直接上了一个台阶。今天我就带你拆开ART的壳,看看它到底是怎么工作的。

7.1 从Dalvik到ART:一次脱胎换骨

Dalvik用的是JIT(Just-In-Time)编译。什么意思呢?App每次启动,Dalvik都会把一部分字节码实时编译成本地机器码。好处是安装快,坏处是每次跑都要编译一遍,CPU和内存都扛不住。

ART(Android Runtime)从Android 4.4开始实验性引入,到5.0正式取代Dalvik。它最大的变化是引入了AOT(Ahead-Of-Time)编译。说白了,就是在安装App的时候,直接把dex字节码编译成本地机器码。这样运行时就不需要再编译了,启动快、执行快。

核心区别一句话总结:Dalvik是“边跑边编译”,ART是“装好就编译好”。

7.2 AOT编译:安装时的“重活”

AOT编译的过程,我画了个图帮你理解:

APK安装 dex2oat 编译 生成 .oat 文件 本地机器码 安装时触发 将dex转为ELF格式 存储在 /data/dalvik-cache 直接执行 对内存的影响 安装时占用额外CPU和存储空间(.oat文件比dex大30%~100%) 运行时内存占用更稳定,不需要JIT缓存区

AOT编译的核心工具是dex2oat。它把APK里的classes.dex文件,编译成ELF格式的.oat文件。这个.oat文件本质上就是一个共享库,里面包含的是本地机器码。

我个人习惯:在分析App启动性能时,我会先检查/data/dalvik-cache/目录下有没有对应的.oat文件。如果文件缺失或损坏,App启动会回退到解释执行,那速度……嗯,你懂的。

7.3 JIT编译:运行时“救火队”

纯AOT有个问题:不是所有代码都会被频繁执行。你想想看,一个App里可能90%的代码用户根本不会用到。把这些代码全部编译成本地机器码,纯粹是浪费存储空间和安装时间。

所以从Android 7.0开始,ART引入了混合编译模式——JIT + AOT 双剑合璧。

JIT编译在运行时工作,它只编译那些“热点代码”(Hot Code)。什么叫热点代码?就是被频繁调用的方法或者循环体。ART会统计每个方法的执行次数,超过阈值就触发JIT编译。

// 伪代码:ART热点检测逻辑
if (method.invokeCount > HOTNESS_THRESHOLD) {
    // 触发JIT编译
    jitCompile(method);
    // 编译后的代码放入JIT代码缓存
    jitCache.put(method, compiledCode);
}

我曾经踩过一个坑:有个App在低端机上频繁GC,排查后发现是JIT代码缓存区设置得太小,热点代码编译完又被回收,导致反复编译。后来调整了dalvik.vm.jit-max-size参数才解决。嗯,JIT缓存大小对内存敏感应用真的很关键。

7.4 混合编译模式:取长补短

Android 7.0引入的混合模式,说白了就是“安装时轻量编译 + 运行时JIT + 后台AOT重编译”。流程是这样的:

  1. 安装阶段:只做轻量级的验证和优化,不进行完整AOT编译。安装速度大幅提升。
  2. 首次运行:使用解释器执行,同时JIT编译器监控热点代码。
  3. 运行中:热点代码被JIT编译并缓存,提升执行效率。
  4. 空闲时(Device Idle):系统后台启动dex2oat,把JIT收集到的热点代码进行AOT重编译,生成优化后的.oat文件。

我画了个流程图,帮你理清这个逻辑:

安装App 轻量验证+优化 解释执行+JIT监控 JIT编译热点 设备空闲时:后台AOT重编译 生成优化版 .oat 文件 安装快 启动快 执行快 存储优化

混合模式的核心价值:既保留了AOT的执行效率,又避免了全量编译带来的安装慢和存储占用大的问题。说白了,就是“既要马儿跑,又要马儿少吃草”。

7.5 ART的核心优势

聊了这么多,我把ART相比Dalvik的核心优势整理成了一张表:

对比维度 Dalvik(JIT) ART(混合模式)
安装速度 快(无需编译) 快(轻量验证)
首次启动速度 慢(JIT编译预热) 快(解释执行+JIT)
持续运行性能 中等(JIT有开销) 高(AOT优化代码)
存储占用 小(无.oat文件) 中等(.oat文件)
内存占用 较高(JIT缓存+解释器) 较低(优化后代码紧凑)
GC效率 一般(CMS收集器) 高(并发收集+紧凑)

除了编译模式,ART在内存管理上还有几个让我印象深刻的改进:

  • 大页支持(Large Pages):ART会尝试使用2MB甚至1GB的大页来映射代码段,减少TLB miss。我在做性能分析时,发现开启大页后,某些计算密集型App的CPU利用率能降5%~8%。
  • 紧凑的对象布局:ART的对象头比Dalvik更小,减少了内存碎片。你想想看,一个App里可能有几十万个对象,每个对象省几个字节,总量就很可观了。
  • 更好的内联优化:AOT编译时,ART会做方法内联、常量折叠等优化。这些优化在Dalvik的JIT模式下很难做彻底,因为JIT编译时间窗口太短。

我建议:如果你在优化App的内存占用,可以关注一下/proc/self/smaps里的“LargePage”字段。如果ART没有成功使用大页,可能是内存碎片导致的,这时候可以考虑调整ro.config.large_region_size参数。

7.6 小结

ART的混合编译模式,本质上是在“安装时间”、“运行性能”和“存储空间”三者之间找平衡。它不像Dalvik那样极端依赖JIT,也不像早期ART那样全量AOT。而是根据实际使用情况,动态调整编译策略。

嗯,理解了ART的架构,你就能明白为什么同样的App,在Android 5.0和Android 10.0上的内存表现会差那么多。下一节我们会深入ART的GC实现,看看它到底是怎么回收内存的。


公众号:蓝海资料掘金营,微信deep3321