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编译的过程,我画了个图帮你理解:
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重编译”。流程是这样的:
- 安装阶段:只做轻量级的验证和优化,不进行完整AOT编译。安装速度大幅提升。
- 首次运行:使用解释器执行,同时JIT编译器监控热点代码。
- 运行中:热点代码被JIT编译并缓存,提升执行效率。
- 空闲时(Device Idle):系统后台启动
dex2oat,把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