ART编译模式学习

ART实际就是Android runtime的缩写,他与dalvik是两种Android的编译模式。

1.诞生

与其说dalvik是与ART共同组成编译模式,不如说dalvik是ART的前身。ART在Android4.4首次被引入为可选项,并且在5.0替换掉了dalvik,他们都是为Android项目量身打造的。

ART 和 Dalvik 是运行 Dex 字节码的兼容运行时,因此针对 Dalvik 开发的应用也能在 ART 环境中运作。不过,Dalvik 采用的一些技术并不适用于 ART。

2.区别

1.预先编译

ART模式与Dalvik模式最大的不同在于,在启用ART模式后,系统在安装应用的时候会进行一次预编译,在安装应用程序时会先将代码转换为机器语言存储在本地,这样在运行程序时就不会每次都进行一次编译了,执行效率也大大提升。并且对比dalvik,ART具有更严格的安装时验证。在安装时,ART 使用设备自带的 dex2oat 工具来编译应用。此实用工具接受 DEX 文件作为输入,并为目标设备生成经过编译的应用可执行文件。该工具应能够顺利编译所有有效的 DEX 文件。但是,一些后处理工具会生成无效文件,Dalvik 可以接受这些文件,但 ART 无法编译这些文件。

2.垃圾回收(GC)时优化

垃圾回收 (GC) 会耗费大量资源,这可能有损于应用性能,导致显示不稳定、界面响应速度缓慢以及其他问题。ART 通过以下几种方式对垃圾回收做了优化:

1.大多采用并发设计,具有一次 GC 暂停
2.并发复制,可减少后台内存使用和碎片
3.GC 暂停的时间不受堆大小影响
4.在清理最近分配的短时对象这种特殊情况中,回收器的总 GC 时间更短
5.优化了垃圾回收的工效,能够更加及时地进行并行垃圾回收,这使得 GC_FOR_ALLOC 事件在典型用例中极为罕见

3.开发和调试方面的优化

ART 提供了大量功能来优化应用开发和调试。

支持采样分析器
一直以来,开发者都使用 Traceview 工具(用于跟踪应用执行情况)作为分析器。虽然 Traceview 可提供有用的信息,但每次方法调用产生的开销会导致 Dalvik 分析结果出现偏差,而且使用该工具明显会影响运行时性能。

ART 添加了对没有这些限制的专用采样分析器的支持,因而可更准确地了解应用执行情况,而不会明显减慢速度。KitKat 版本为 Dalvik 的 Traceview 添加了采样支持。

支持更多调试功能
ART 支持许多新的调试选项,特别是与监控和垃圾回收相关的功能。例如可以:

1.查看堆栈跟踪中保留了哪些锁,然后跳转到持有锁的线程。
2.询问指定类的当前活动的实例数、请求查看实例,以及查看使对象保持有效状态的参考。
3.过滤特定实例的事件(如断点)。
4.查看方法退出(使用“method-exit”事件)时返回的值。
5.设置字段观察点,以在访问和/或修改特定字段时暂停程序执行。

优化了异常和崩溃报告中的诊断详细信息
当发生运行时异常时,ART 会为您提供尽可能多的上下文和详细信息。ART 会提供 java.lang.ClassCastException、java.lang.ClassNotFoundException 和 java.lang.NullPointerException 的更多异常详细信息。(较高版本的 Dalvik 会提供 java.lang.ArrayIndexOutOfBoundsException 和 java.lang.ArrayStoreException 的更多异常详细信息,这些信息现在包括数组大小和越界偏移量;ART 也提供这类信息。)

3.一些基本名词

1.dex文件

App所有java源代码编译后生成众多class文件,由DX/D8,编译为一个/多个(multiDex)dex文件,由Android虚拟机编译执行

2.odex文件

dex文件经过验证和优化后的产物,art下的odex文件包含经过AOT编译后的代码以及dex的完整内容,但Android8.0之后odex中的dex内容移动到了.vdex文件

3.art文件

art下根据配置文件生成odex文件时同时生成.art文件,主要是为了提升运行时加载odex中热点代码的速度,包含了类信息和odex中热点方法的索引,运行App时会首先根据这个文件来加载odex中已经编译过的代码;执行过speed-profile命令的app才会生成.art文件

4.JIT编译(Just In Time)

由于解释器方式运行太慢引入,对于频繁运行的热点代码(判定标准一般是在某个时间段内执行次数达到某个阈值)进行实时编译(在ART下以方法为粒度)执行,并且缓存JIT编译后的代码在内存中用于下次执行。由于以方法为粒度(ArtMethod)进行编译,JIT编译较于解释器可以生成效率更高的代码,运行更快

5.AOT编译(Ahead-Of-Time)

应用安装时全量编译所有代码为本地机器码,运行时直接执行机器码。

6.JIT编译与AOT编译对比

JIT编译

优势

1.能够根据当前硬件状况实时编译生成最优机器指令(ps. AOT 也能够作到,在用户使用是使用字节码根据机器状况在作一次编译)
2.能够根据当前程序的运行状况生成最优的机器指令序列
3.当程序须要支持动态连接时,只能使用 JIT
4.能够根据进程中内存的实际状况调整代码,使内存可以更充分的利用

劣势

1.编译必须要占用线程资源,导致卡顿
2.因为编译时间须要占用运行时间,对于某些代码的编译优化不能彻底支持,须要在程序流畅和编译时间之间作权衡
3.在编译准备和识别频繁使用的方法须要占用时间,使得初始编译不能达到最高性能
4.由于 JIT Code Cache 是内存缓存,因此每次运行都需要重新编译

AOT编译

优势

1.在程序运行前编译,能够避免在运行时的编译性能消耗和内存消耗
2.能够在程序运行初期就达到最高性能
3.能够显著的加快程序的启动

劣势

1.应用安装和系统升级之后的应用优化比较耗时(重新编译,把程序代码转换成机器语言)
2.优化后的文件会占用额外的存储空间(缓存转换结果)

4.运作方式

一.4.4~7.0

最开始ART只采用AOT编译,在App安装时就编译所有代码存储在本地,打开App直接运行,这样做的优点是应用运行速度变快,缺点也很明显,App安装时间明显变长,而且占用存储空间较大。

二.7.0

Android N之后对于ART进行改动,重新引入了JIT编译,结合使用AOT/JIT混合编译,主要机制如下:

安装时不进行任何编译,前几次运行仅通过解释器解释运行,同时对热点代码进行JIT编译,并将这些代码的相关信息记录在一个配置文件里(data/misc/profile/cur/0/…)

设备处于空闲和充电状态时,编译守护进程读取配置文件对热点代码进行AOT编译并写入到app对应的odex文件中

再次启动应用后优先使用AOT编译过的代码,否则使用解释器+JIT编译,重复这个过程

对于一些庞大的APP,比如淘宝,有些功能可能你一辈子都不会用到,根据上述策略这部分代码就不会被编译保存,从而减少了存储空间的占用。另外,在系统升级时也避免了全量编译所有现存应用造成的时间空间消耗。

三.8.0

Android 8.0引入了.vdex文件,它里面包含 APK 的未压缩 DEX 代码,以及一些用于加快验证速度的元数据。怎么理解呢?这里我们需要补充一下对dex文件的编译配置和系统ROM中各类应用的默认编译方式:

编译选项(compiler filters):

1.verify:只对 DEX 文件进行代码验证,校验各部分合法性。

2.quicken:在verify的基础上优化一些 DEX 指令,提升解释器性能。

3.speed:在verify的基础对所有方法进行 AOT 编译。

4.speed-profile:在verify的基础对配置文件中列出的方法(热点方法)进行 AOT 编译。

系统ROM中各类应用默认编译方式:

1.启动类路径代码(用于启动系统的部分代码):默认使用 speed 编译过滤器进行编译

2.系统服务器代码(比如电量、多媒体服务代码):默认使用 speed 编译过滤器进行编译

3.产品专属的核心应用(比如goole服务框架):默认使用 speed 编译过滤器进行编译

4.所有第三方应用:默认使用 quicken 编译过滤器进行编译

所有第三方应用最开始都是通过quiken模式只进行了dex校验和一些指令优化,.vdex文件存放的就是经过校验后的dex代码,以便在对热点代码进行AOT编译时避免重复验证,加快速度。

四.混合编译的优势

1.应用安装时间过长;在 N 之前,应用在安装时需要对所有 ClassN.dex 做 AOT 机器码编译,类似微信这种比较大型的 APP 可能会耗时数分钟。但是往往我们只会使用一个应用 20% 的功能,剩下的 80% 我们付出了时间成本,却没带来太大的收益。

2.降低占 ROM 空间;同样全量编译 AOT 机器码,12M 的 dex 编译结果往往可以达到 50M 之多。只编译用户用到或常用的 20% 功能,这对于存储空间不足的设备尤其重要。

3.提升系统与应用性能;减少了全量编译,降低了系统的耗电。在 boot.art 的基础上,每个应用增加了 base.art (这块后面会详细解析), 通过预加载与缓存提升应用性能。

4.快速的系统升级;以往厂商 ota 时,需要对安装的所有应用做全量的 AOT 编译,这耗时非常久。事实上,同样只有 20% 的应用是我们经常使用的,给不常用的应用,不常用的功能付出的这些成本是不值得的。

综合下来,混合编译的模式因为在安装的时候不需要进行编译,安装速度会很快。在以后运行app时,会抓取热点数据进行存储并执行AOT编译优化体验。所以在前几次打开应用时速度会相对较慢,在操作次数多了后性能将会跟上。

过滤器

ART 如何编译 DEX 代码还有个compile filter以参数的形式来决定:从 Android O 开始,有四个官方支持的过滤器:

verify:只运行 DEX 代码验证。

quicken:运行 DEX 代码验证,并优化一些 DEX 指令,以获得更好的解释器性能。

speed-profile:运行 DEX 代码验证,并对配置文件中列出的方法进行 AOT 编译。

speed:运行 DEX 代码验证,并对所有方法进行 AOT 编译。

verify 和quicken 他俩都没执行编译,之后代码执行需要跑解释器。而speed-profile 和 speed 都执行了编译,区别是speed-profile根据profile记录的热点函数来编译,属于部分编译,而speed属于全编。

所以

执行效率上:

verify < quicken < speed-profile < speed

编译速度上:

verify > quicken > speed-profile > speed

你可能感兴趣的:(学习,android,java)