一、ART概览:从Dalvik到ART的演进
说起Android Runtime,我脑子里第一个蹦出来的词就是「进化」。从2008年Android 1.0发布时的Dalvik虚拟机,到2014年Android 5.0全面转向ART,这中间的变化,说白了就是一场性能革命。
我记得刚入行那会儿,团队里还在用Dalvik。每次编译完APK,测试同事总会抱怨:「这应用启动怎么这么慢?」嗯,那时候我们只能苦笑——Dalvik的JIT编译模式,天生就有这个短板。
Dalvik时代的痛点
Dalvik虚拟机用的是JIT(Just-In-Time)编译。什么意思呢?应用每次启动时,Dalvik才会把热点代码编译成机器码。你想想看,每次打开应用都要现编译,启动速度能快才怪。
我在项目中遇到过最典型的场景:一个电商应用,首页加载要等3-4秒。用户早就不耐烦了。后来我们做了各种优化,但Dalvik的JIT模式决定了——你再怎么优化,启动速度的上限就在那儿。
核心问题:Dalvik的JIT编译是「边跑边编译」,每次启动都要重复这个过程。电量消耗大,CPU占用高,用户体验差。
ART的诞生
ART(Android Runtime)从Android 4.4开始作为实验性功能出现,到5.0正式取代Dalvik。它的核心变化就一个:把JIT换成了AOT(Ahead-Of-Time)预编译。
说白了,就是在应用安装时,ART直接把DEX字节码编译成机器码。以后每次启动,直接执行机器码就行,不用再编译了。
我曾经在项目里做过对比测试:同一个应用,Dalvik环境下启动需要2.8秒,ART环境下只要0.9秒。这个差距,用户是能明显感知到的。
个人经验:ART的AOT编译虽然让安装时间变长了(因为安装时要编译),但换来的是日常使用的流畅体验。我建议在开发阶段用debug包,发布时用release包,这样既能保证开发效率,又能让用户享受AOT的好处。
二、ART核心设计目标
ART的设计目标,我总结为三个词:快、省、稳。
1. 启动速度更快
这个不用多说,AOT预编译直接解决了Dalvik的启动慢问题。但ART不只是做了AOT这么简单。
ART还引入了Profile-Guided Optimization(PGO)。什么意思呢?系统会记录用户常用的应用和代码路径,然后针对这些热点代码做更激进的优化。不常用的代码,就保持解释执行或者用JIT编译。
我在做系统优化时发现,PGO对冷启动的优化效果特别明显。比如微信,第一次打开可能要1.2秒,但用了PGO之后,能降到0.6秒左右。
2. 内存占用更省
ART在内存管理上做了很多改进。比如紧凑的堆布局,减少了内存碎片。还有更高效的GC(垃圾回收),暂停时间从Dalvik的几十毫秒降到了几毫秒。
我记得有一次,团队在优化一个图片处理应用。Dalvik下GC频繁触发,导致界面卡顿。换成ART后,GC次数减少了40%,卡顿基本消失了。
关键数据:ART的GC暂停时间平均在2-5ms,而Dalvik通常在10-30ms。这个差距在动画场景下特别明显。
3. 系统更稳定
ART的稳定性提升,主要体现在异常处理和类型安全上。ART在编译阶段就能发现很多类型错误,而不是等到运行时才崩溃。
我曾经踩过一个坑:Dalvik下,一个泛型类型转换错误,直到用户操作到某个特定页面才崩溃。换成ART后,安装时就直接报错了。虽然开发时觉得烦,但想想看——总比用户遇到闪退强吧?
避坑指南:我曾经在项目里遇到过ART编译时OOM的问题。原因是应用太大,AOT编译需要大量内存。解决方案是开启android:extractNativeLibs="true",或者使用Android 7.0引入的混合编译模式。
三、ART与JVM的对比
很多人问我:「ART和JVM到底有什么区别?」嗯,这个问题问得好。虽然两者都是运行时环境,但设计思路完全不同。
核心差异一览
| 对比维度 | ART | JVM(标准实现) |
|---|---|---|
| 字节码格式 | DEX(Dalvik Executable) | Class/Java字节码 |
| 编译策略 | AOT + JIT + 解释执行 | JIT + 解释执行 |
| 内存模型 | 紧凑堆、低碎片 | 传统堆、碎片较多 |
| GC策略 | 并发标记、低暂停 | 分代收集、暂停较长 |
| 多线程支持 | 基于Linux pthread | 基于操作系统线程 |
| 资源限制 | 严格(移动设备) | 宽松(服务器/桌面) |
字节码格式的不同
JVM用的是标准的Java字节码,每个Class文件对应一个类。而ART用的是DEX格式,把所有类的字节码打包到一个文件里。
这样做的好处很明显:减少IO次数。你想想看,一个应用可能有几千个类,如果每个类一个文件,加载时得打开几千次文件。DEX格式一次加载就搞定了。
我在做性能分析时发现,DEX格式的加载速度比Class格式快3-5倍。这在移动设备上尤其重要——毕竟手机的IO性能比不上服务器。
编译策略的差异
JVM主要靠JIT编译,热点代码在运行时被编译成机器码。ART则更灵活:
- 安装时:AOT编译,生成机器码
- 运行时:JIT编译热点代码
- 空闲时:后台重新编译优化
这种混合策略,说白了就是「既要又要还要」——既要启动快,又要运行快,还要省电。
我的建议:如果你是从JVM转过来做Android开发,记住一点——ART对内存更敏感。在JVM里你可能习惯new一大堆对象,但在ART里,对象创建和GC的开销更大。尽量复用对象,减少临时分配。
内存模型的差异
ART的内存模型比JVM更紧凑。它使用了TLAB(Thread-Local Allocation Buffer)和紧凑堆技术,减少了内存碎片。
我记得在优化一个视频播放器时,Dalvik下内存碎片率高达15%,导致频繁触发GC。换成ART后,碎片率降到了3%以下,播放流畅多了。
四、知识体系总览
下面这张图,是我整理的ART核心知识体系。你可以把它当作学习路线图:
这张图展示了ART的三个核心模块:编译策略、内存管理、字节码与执行。它们共同支撑起了ART「快、省、稳」的设计目标。
五、总结
从Dalvik到ART的演进,本质上是Android系统对性能追求的体现。ART通过AOT预编译、紧凑内存管理和灵活的编译策略,解决了Dalvik时代的痛点。
我个人觉得,理解ART的关键在于把握它的设计哲学:在资源受限的移动设备上,用编译时的开销换取运行时的效率。这个思路,其实在很多系统优化中都能看到影子。
嗯,这一章的内容就到这里。下一章我们会深入ART的编译流程,看看DEX字节码到底是怎么变成机器码的。