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.NullPointerExceptionjava.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位元码

实用干货

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还是要做,如果第三方足够多也会占用大量的开机时间,这里要怎么处理?交给大家思考吧! :)