第二章 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进程」的过程。我把它拆成几个关键步骤:

  1. Zygote启动——init进程启动Zygote,Zygote加载ART Runtime、预加载常用类
  2. App进程fork——Zygote收到启动App的请求,fork出子进程
  3. Runtime初始化——子进程执行Runtime::Init,初始化堆、GC、线程池
  4. 类加载——ClassLinker加载App的DEX/APK,解析类结构
  5. 方法编译——根据编译策略,JIT或AOT编译热点方法
  6. 执行入口——调用ActivityThread.main(),App正式启动

嗯,这里要注意:Zygote fork出来的进程,会继承父进程的堆状态。这意味着Zygote里预加载的类,在子进程里是「现成」的。但如果你在Zygote里做了不该做的事(比如启动线程),那所有子进程都会带着这个线程跑。我见过一个案例:某厂商在Zygote里加了个监控线程,结果每个App都多了一个线程,内存开销直接翻倍。

核心要点:ART启动流程的关键在于「预加载」和「按需编译」。预加载减少了类加载时间,按需编译平衡了安装速度和运行性能。理解这两点,你就抓住了ART优化的命门。

2.4 架构总览图

下面这张图是我自己整理的ART架构总览。它把上面讲的三层结构和核心组件串在了一起。你盯着看两分钟,应该能对ART有个整体印象。

ART架构总览 应用层 (Application Layer) Java/Kotlin代码 · Android Framework · App进程 通过JNI与运行时层交互 运行时层 (Runtime Layer) Compiler AOT / JIT / 混合编译 Runtime 类加载 · 方法分派 · 线程管理 GC CMS / CC / Generational CC 共享:堆内存 · 类元数据 · 编译缓存 系统层 (System Layer) — Linux内核 · 进程调度 · 内存管理

这张图里,我特意把三个核心组件之间的交互箭头画了出来。你可能会问:为什么Compiler和GC之间没有直接箭头?嗯,它们确实不直接通信。Compiler只管编译,GC只管回收,中间通过Runtime协调。这种「各司其职」的设计,让ART的模块化程度很高,但也意味着Runtime一旦出问题,整个系统就瘫痪了。

2.5 小结

这一章我们聊了ART的层次结构、三个核心组件,以及启动流程。说白了,ART就是个「分层协作」的系统——应用层只管写代码,运行时层负责执行和调度,系统层提供底层支持。Compiler、Runtime、GC各管一摊,但配合得好才能让App跑得又快又稳。

我个人觉得,理解ART架构的关键不在于记住每个组件的细节,而在于明白它们之间的「依赖关系」和「数据流向」。你想想看,如果GC在回收对象时,Compiler还在编译同一个类的方法,会发生什么?嗯,这就是下一章要聊的「同步与互斥」问题了。

个人建议:如果你刚开始接触ART源码,建议从Runtime的初始化流程入手。它像一根线,把Compiler、GC、类加载器都串了起来。我当初就是这么干的,效果不错。


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