第二章 ART架构总览:层次结构、核心组件与启动流程
好,我们正式开始聊ART的架构。说实话,我第一次完整看完ART源码时,脑子里就一个感觉——这玩意儿比Dalvik复杂太多了。但别怕,复杂的东西往往有规律可循。今天我就带你把这层窗户纸捅破。
2.1 ART的层次结构
ART不是个单一体,它是个分层系统。我个人习惯把它分成三层:
- 应用层——你写的Java/Kotlin代码,最终跑在这里
- 运行时层——ART Runtime核心,负责执行、内存管理、异常处理
- 系统层——与Linux内核交互,管理进程、线程、文件I/O
你想想看,这三层之间怎么通信?说白了就是通过JNI和系统调用。我在项目中遇到过一个问题:某个自定义View频繁触发GC,导致掉帧。查到最后发现是底层JNI调用没处理好引用,导致对象无法回收。嗯,这种坑踩过一次就记住了。
核心要点:ART的分层设计让各模块职责清晰,但也意味着跨层调用需要格外小心。尤其是JNI层的引用管理,稍不留神就是内存泄漏。
2.2 核心组件详解
ART有三个核心组件:Compiler、Runtime、GC。这三兄弟配合得好,App就流畅;配合不好,卡顿、ANR就找上门了。
2.2.1 Compiler(编译器)
编译器负责把DEX字节码转成本地机器码。ART里有两种编译模式:
- AOT(Ahead-Of-Time)——安装时全量编译,启动快,但安装慢、包大
- JIT(Just-In-Time)——运行时按需编译,安装快,但启动慢
从Android 7.0开始,ART引入了混合编译模式。我记得当时Google的工程师在IO大会上说了一句话:「我们既要安装快,又要运行快。」听起来像悖论,但他们做到了——通过Profile Guided Compilation(PGC),先JIT跑一段时间,收集热点代码,再AOT编译。
避坑指南:我曾经在优化冷启动时,发现某个第三方库的初始化方法被标记为「永不编译」。查了半天,原来是它的代码路径太冷门,Profile没收集到。解决方案是手动触发一次预编译,或者调整编译阈值。
2.2.2 Runtime(运行时)
Runtime是ART的大脑。它负责:
- 类加载与链接
- 方法分派与执行
- 异常处理
- 线程管理
- 与Compiler、GC的协调
Runtime里有个关键概念叫Thread Root。每个Java线程在ART里都有一个对应的Native线程,它们共享堆内存,但各自维护自己的栈。为什么会这样?因为GC需要知道哪些对象是「活的」,而线程栈就是最重要的根集合之一。
2.2.3 GC(垃圾回收器)
GC这块水很深。ART的GC经历了三代演变:
| GC类型 | 特点 | 适用场景 |
|---|---|---|
| CMS(Concurrent Mark Sweep) | 并发标记、不压缩 | 低延迟场景 |
| CC(Concurrent Copying) | 并发复制、压缩 | 减少碎片、提升分配效率 |
| Generational CC | 分代回收、新生代快速回收 | 大多数App场景 |
我个人最常用的是Generational CC。它在Android 10之后成为默认GC,对大多数App来说表现很均衡。但要注意,如果你的App有大量短生命周期对象,Generational CC的「晋升」机制可能会导致老年代快速膨胀。我曾经在一个图片编辑App里遇到过这个问题——频繁创建临时Bitmap,结果老年代GC频繁触发,界面卡成PPT。
警告:不要盲目切换GC策略。ART的GC选择是全局的,修改它会影响所有App。除非你确定你的场景有特殊需求,否则保持默认就好。
2.3 启动流程概览
ART的启动流程,说白了就是「从Zygote到App进程」的过程。我把它拆成几个关键步骤:
- Zygote启动——init进程启动Zygote,Zygote加载ART Runtime、预加载常用类
- App进程fork——Zygote收到启动App的请求,fork出子进程
- Runtime初始化——子进程执行Runtime::Init,初始化堆、GC、线程池
- 类加载——ClassLinker加载App的DEX/APK,解析类结构
- 方法编译——根据编译策略,JIT或AOT编译热点方法
- 执行入口——调用ActivityThread.main(),App正式启动
嗯,这里要注意:Zygote fork出来的进程,会继承父进程的堆状态。这意味着Zygote里预加载的类,在子进程里是「现成」的。但如果你在Zygote里做了不该做的事(比如启动线程),那所有子进程都会带着这个线程跑。我见过一个案例:某厂商在Zygote里加了个监控线程,结果每个App都多了一个线程,内存开销直接翻倍。
核心要点:ART启动流程的关键在于「预加载」和「按需编译」。预加载减少了类加载时间,按需编译平衡了安装速度和运行性能。理解这两点,你就抓住了ART优化的命门。
2.4 架构总览图
下面这张图是我自己整理的ART架构总览。它把上面讲的三层结构和核心组件串在了一起。你盯着看两分钟,应该能对ART有个整体印象。
这张图里,我特意把三个核心组件之间的交互箭头画了出来。你可能会问:为什么Compiler和GC之间没有直接箭头?嗯,它们确实不直接通信。Compiler只管编译,GC只管回收,中间通过Runtime协调。这种「各司其职」的设计,让ART的模块化程度很高,但也意味着Runtime一旦出问题,整个系统就瘫痪了。
2.5 小结
这一章我们聊了ART的层次结构、三个核心组件,以及启动流程。说白了,ART就是个「分层协作」的系统——应用层只管写代码,运行时层负责执行和调度,系统层提供底层支持。Compiler、Runtime、GC各管一摊,但配合得好才能让App跑得又快又稳。
我个人觉得,理解ART架构的关键不在于记住每个组件的细节,而在于明白它们之间的「依赖关系」和「数据流向」。你想想看,如果GC在回收对象时,Compiler还在编译同一个类的方法,会发生什么?嗯,这就是下一章要聊的「同步与互斥」问题了。
个人建议:如果你刚开始接触ART源码,建议从Runtime的初始化流程入手。它像一根线,把Compiler、GC、类加载器都串了起来。我当初就是这么干的,效果不错。
公众号:蓝海资料掘金营,微信deep3321