ART和Dalvik
此页内容部分翻译自:https://source.android.com/devices/tech/dalvik/
ART,为 Android runtime的简称,它为 Andriod中应用程序和系统服务提供一种新的运行模式。ART和它的前任 Dalvik都是为 Android专门开发的。ART兼容 Dalvik的文件格式和 Dex文件规范。
既然 ART和 Dalvik都兼容 Dex文件规范,所以之前为 Dalvik开发的应用理应在 ART上也能正常运作。不过,仍有一些 Dalvik上的开发技巧在 ART上是不能使用的,可以点击这里作进一步了解。
目录 |
ART特性
这里列出的是 ART的一些主要特性:
AOT(Ahead-of-time)“运行前”编译
ART 采用的是“运行前”(AOT)编译,这样使得运行效率得到提升。此外,与 Dalvik相比,ART在应用安装时有着更为严格的校验机制。
当应用安装时,ART通过 dex2oat工具对应用进行编译。这个工具可以把输入的 DEX文件编译生成当前机器兼容的可直接运行的执行机器码。理论上说,这个工具对所有正确的 DEX文件都应该是兼容的,但有些被预处理过的、不正确的 DEX文件,可能会出错(即使之前这些文件可以被 Dalvik相对较宽的校验机制通过)。更多内容
更优秀的垃圾回收机制
垃圾回收(GC)会影响应用的性能体验,导致界面卡顿、反应迟缓等等。ART在垃圾回收上的提升体现在以下几个方面:
- GC的中断暂停从 Dalvik时的 2次减少为 1次
- 在 GC暂停时仍可并行处理其他事务
- 在回收刚创建的、生命周期较短的对象时,消耗的时间更短
- 更及时有效的回收机制,使得 GC_FOR_ALLOC事件更少发生
- 更少的内存占用和内存碎片
开发调试提升
ART拥有一系列的特性,使得应用的开发和调试更为方便有效。
支持采样分析
在此之前,开发者一般使用 Traceview。Traceview在提供的信息很有用,在 Dalvik中可以细化到到函数调用层面,同时在分析时性能会显著变慢。
ART支持专用的采样分析,规避了上面这些不足。它可以为应用提供更准确的度量,同时不会出现性能上的显著下降。在 KitKat版本上已经添加了对 Traceview的支持。
支持更多的调试特性
ART支持一系统的调试选项,尤其是 监视和 垃圾回收相关方面。比如:
- 查看堆栈中锁的保持者
- 查看当前某个类的活动对象数量,并查看这个对象及其引用
- 对特定对象进行事件过滤
- 查看某一函数的返回值
- 设置动态断点
更详尽的错误及异常信息
当运行抛出异常时,ART 会提供大量的运行信息。 ART 扩充细化了以下异常:java.lang.ClassCastException, java.lang.ClassNotFoundException, java.lang.NullPointerException。(ART和最新的 Dalvik一样,为 数组越界和 边界溢出 java.lang.NullPointerException、 java.lang.NullPointerException进行了扩充。)
例如,java.lang.NullPointerException 异常信息包含这个空指针发生在哪个应用,并且这个应用要尝试操作写入哪个变量,或者在调用哪个函数。下面是一个例子:
java.lang.NullPointerException: Attempt to write to field 'int android.accessibilityservice.AccessibilityServiceInfo.flags' on a null object reference
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.Object.toString()' on a null object reference
当应用出现 NE时,ART也能够提供相关信息,包括 Java层和 Native层的堆栈等等。
问题反馈
如果你遇到任何非 JNI类的问题,请通过 AOSP http://b.android.com 反馈。反馈时请包含 "adb bugreport"内容并提供对应 App在 Google Play上的链接;或者,如果可以,请把相关 APK作为附件上传。另外,请注意,反馈内容(包括附件)在网上都是公开的。
目录
实用干货
Dalvik和 ART dex文件概述
所有的apk内部包含一个 classes.dex文件,这个文件的 Magic Number(幻数)是:
ubyte[8] DEX_FILE_MAGIC = { 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 }
= "dex\n035\0"
在 Dalvik上,apk包里的 dex文件在安装的时候会通过 dexopt 转化成另一个格式,叫odex(Opitimized dex),存在 /data/dalvik-cache里面,如:
/data/dalvik-cache/data@app@com.wochacha-1.apk@classes.dex
——文件后缀还是 .dex,命令方式是 apk的存放路径 '/'-> '@' + classes.dex
同时 Magic Number变成:{ 0x64 0x65 0x79 0x0a 0x30 0x33 0x36 0x00 } = "dey\n036\0"
这个文件仍不能够直接运行,每次在运行时还要编译生成本地可执行的机器码,因此效率较 ART低
这时,可能会疑惑这个文件存在的必要性,实际上这个文件是针对当前机器的硬件对 dex文件进行了定制化,也就是说把这个放到别的设备上,不一定能运行
在要编译 rom的时候,如果"WITH_DEXPREOPT=true",会在 /system/app/下同时生成 .apk和 .odex文件(注意,这里后缀又用的 .odex,但实际上和系统在 /data/dalvik-cache/下的 .dex文件是一样的)
在 ART上,apk包里的 dex文件在安装的时候通过 dex2oat,也会生成一个后缀为 .dex的文件,放在 /data/dalvik-cache中,如:
/data/dalvik-cache/arm/system@app@Bluetooth@Bluetooth.apk@classes.dex
/data/dalvik-cache/arm64/system@vendor@app@ims@ims.apk@classes.dex
——文件后缀,和命名方式和 Dalvik完全一样,但其实这是完全不同的一个东西了,
文件格式也完全不同,因为这其实就是一个实打实的 elf文件,Magic Number:{ 0x75, 0x45, 0x4c, 0x46, ...} = " ELF..."
这个文件已经可以直接在机器上运行
编译选项
WITH_DEXPREOPT
使能编译时生成 OAT,避免第一次开机时编译耗时,但会增大 system分区的空间消耗
DONT_DEXPREOPT_PREBUILTS
使能后,将不会对 Android.mk中包含了 include $(BUILD_PREBUILT)的 Apk进行 oat,例如 Gmail,它很可能会在后期通过商店自行升级,而升级后系统中的 oat文件则没有意义了,但又无法删除,会造成空间的浪费(oat比dex文件要大)
WITH_DEXPREOPT_BOOT_IMG_ONLY
仅仅针对 boot.img进行oat优化(boot.img中包含 boot.art和 boot.oat)
DEX_PREOPT_DEFAULT
LOCAL_DEX_PREOPT
ture|false|nostripping
可用于各个 Android.mk,对每个 package进行单独配置,当设置为 true时,dex文件将会从 apk中剔除,如果不想剔除可使用 nostripping
WPRODUCT__DEX_PREOPT_*
PRODUCT_DEX_PREOPT_BOOT_FLAGS
这里的参数将会传至 dex2oat,控制 boot.img的编译优化行为
PRODUCT_DEX_PREOPT_DEFAULT_FLAGS
控制除 boot.img外,其他(如 jar, apk)的 OAT编译行为
例如:
PRODUCT_DEX_PREOPT_DEFAULT_FLAGS := --compiler-filter=interpret-only
$(call add-product-dex-preopt-module-config,services,--compiler-filter=space)
WITH_DEXPREOPT_PIC
ture|false
使能 position-independent code,这样在dex2oat编译生成的 odex文件在运行时将不必再从 /system下拷贝到 /data/dalvik-cache/目录下,
可以节省 /data空间
WITH_ART_SMALL_MODE
true|false
设置为 true时,将只编译处于 boot classpath里的类,其他的均不编译,这样既能加快第一次开机时间,因为大部分必要的类已经编译过了;
同时也能节省不少空间,因为 APP都未进行编译
缺点是可能损失一性能,这可能要平时觉察不出,但在跑分软件上会有所体现
经典配置
为了提高第一次开机速度,WITH_DEXPREOPT是必须使能的,这样则在编译阶段会完成 dex2oat的操作,避免在开机时间去做这个转码,节省了开机时间(6min以上缩短2min内)。
但会引起一个缺点,那就是 apk中还是包含了 class.dex(dexopt生成的),同时在对应的apk文件夹中又生成了已经转码成oat的 class.odex(dex2oat生成的),相当于这部分重复,造成了大量的空间浪费。
为了把 apk包里的 class.dex去除,节省空间,可以打开 DEX_PREOPT_DEFAULT := ture。
然而,这样开机速度是快了,而且节省了不少system空间,但开机后,我们会发现即使在 system中已经存在 class.odex的 apk,第一次开机后还是会在 /data下面生成 class.odex,如data/dalvik-cache/arm64/system@app@Music@Music.apk@classes.dex,这是何解?原来 Google为了提高安全性,在每一台机器开机时都会在之前的机器码加一个随机的偏移量,这个偏移量是随机的,每台机器都不相同,而 data分区下的这些文件就是从 system下的 class.odex加上偏移而来。
static int32_t ChooseRelocationOffsetDelta(int32_t min_delta, int32_t max_delta) { CHECK_ALIGNED(min_delta, kPageSize); CHECK_ALIGNED(max_delta, kPageSize); CHECK_LT(min_delta, max_delta); std::default_random_engine generator; generator.seed(NanoTime() * getpid()); std::uniform_int_distribution<int32_t> distribution(min_delta, max_delta); int32_t r = distribution(generator); if (r % 2 == 0) { r = RoundUp(r, kPageSize); } else { r = RoundDown(r, kPageSize); } CHECK_LE(min_delta, r); CHECK_GE(max_delta, r); CHECK_ALIGNED(r, kPageSize); return r; }
所以如果还想节省这一个空间,那我们可以把这个偏移量设定为0。这样,在开机阶段,pm检测到 system下的 class.odex是up-to-date,就不会在 /data下重新生成了。
做了以上三个更改后,解决方案堪称完美,但其实还有一个问题,那就是在线升级后第三方的apk的dex2oat还是要做,如果第三方足够多也会占用大量的开机时间,这里要怎么处理?交给大家思考吧! :)