ART前世今生
虽然Android应用大多用Java/Kotlin编写,但是实际上Android虚拟机并不使用JVM字节码,而是将Class文件通过DX编译器(现已换成D8)编译为更加紧凑的dex文件,然后由虚拟机执行。
最开始的Android虚拟机是Dalvik,ART虚拟机由Android4.4被引入成为可选项,在Android5.0之后替换掉了Dalvik,并且在Android7.0和8.0分别进行了一系列改动,关于ART虚拟机的说明官方文档有提到,不过并不是那么好理解。
基本概念和名词
-
.dex文件:App所有java源代码编译后生成众多class文件,由DX/D8,编译为一个/多个(multiDex)dex文件,由Android虚拟机编译执行
-
.odex文件:dex文件经过验证和优化后的产物,art下的odex文件包含经过AOT编译后的代码以及dex的完整内容,但Android8.0之后odex中的dex内容移动到了.vdex文件
-
.art文件:art下根据配置文件生成odex文件时同时生成.art文件,主要是为了提升运行时加载odex中热点代码的速度,包含了类信息和odex中热点方法的索引,运行App时会首先根据这个文件来加载odex中已经编译过的代码
-
解释器(Interpreter):用于程序运行时对代码进行逐行解释,翻译成对应平台的机器码执行
-
JIT编译(Just In Time):由于解释器方式运行太慢引入,对于频繁运行的热点代码(判定标准一般是在某个时间段内执行次数达到某个阈值)进行实时编译(在ART下以方法为粒度)执行,并且缓存JIT编译后的代码在内存中用于下次执行。由于以方法为粒度(ArtMethod)进行编译,JIT编较于解释器可以生成效率更高的代码,运行更快。
-
AOT编译(Ahead-Of-Time):应用安装时全量编译所有代码为本地机器码,运行时直接执行机器码。
ART 如何运作
4.4~7.0
最开始ART只采用AOT编译,在App安装时就编译所有代码存储在本地,打开App直接运行,这样做的优点是应用运行速度变快,缺点也很明显,App安装时间明显变长,而且占用存储空间较大。
7.0
Android N之后对于ART进行改动,重新引入了JIT编译,结合使用AOT/JIT混合编译,主要机制如下:
-
安装时不进行任何编译,前几次运行仅通过解释器解释运行,同时对热点代码进行JIT编译,并将这些代码的相关信息记录在一个配置文件里
-
设备处于空闲和充电状态时,编译守护进程读取配置文件对热点代码进行AOT编译并写入到app对应的odex文件中
-
再次启动应用后优先使用AOT编译过的代码,否则使用解释器+JIT编译,重复这个过程
对于一些庞大的APP,比如淘宝,有些功能可能你一辈子都不会用到,根据上述策略这部分代码就不会被编译保存,从而减少了存储空间的占用。另外,在系统升级时也避免了全量编译所有现存应用造成的时间空间消耗。
8.0
Android 8.0引入了.vdex文件,它里面包含 APK 的未压缩 DEX 代码,以及一些用于加快验证速度的元数据。怎么理解呢?这里我们需要补充一下对dex文件的编译配置和系统ROM中各类应用的默认编译方式:
编译选项(compiler filters):
-
verify:只对 DEX 文件进行代码验证,校验各部分合法性。
-
quicken:在verify的基础上优化一些 DEX 指令,提升解释器性能。
-
speed:在verify的基础对所有方法进行 AOT 编译。
-
speed-profile:在verify的基础对配置文件中列出的方法(热点方法)进行 AOT 编译。
系统ROM中各类应用默认编译方式:
-
启动类路径代码(用于启动系统的部分代码):默认使用 speed 编译过滤器进行编译
-
系统服务器代码(比如电量、多媒体服务代码):默认使用 speed 编译过滤器进行编译
-
产品专属的核心应用(比如goole服务框架):默认使用 speed 编译过滤器进行编译
-
所有第三方应用:默认使用 quicken 编译过滤器进行编译
所有第三方应用最开始都是通过quiken模式只进行了dex校验和一些指令优化,.vdex文件存放的就是经过校验后的dex代码,以便在对热点代码进行AOT编译时避免重复验证,加快速度。
总结
扯了这么多淡实际上核心内容就一句话:App安装时不编译代码只校验合法性,运行时通过解释器执行,将运行频繁的代码进行编译放到内存缓存并且记录在本地配置文件,后台线程编译配置文件记录的方法存放到.odex文件,再次运行App时优先读.odex文件中编译后的代码,然后重复这个过程。
网友评论