配置 ART

本页讨论如何配置 Android 运行时 (ART) 及其编译选项。此处讨论的主题包括系统镜像预编译的配置、dex2oat 编译选项,以及如何在系统分区空间、数据分区空间和性能之间进行权衡。

请参阅 ART 和 Dalvik 以及 Dalvik 可执行格式 以使用 ART。请参阅 在 Android 运行时 (ART) 上验证应用行为,以确保您的应用正常运行。

ART 的工作原理

ART 使用预先 (AOT) 编译,并且从 Android 7 开始,它使用 AOT 编译、即时 (JIT) 编译和解释的混合组合,并且 AOT 编译可以是配置文件引导的。所有这些执行模式的组合都是可配置的,将在本节中讨论。例如,Pixel 设备配置为在以下流程中工作

  1. 应用程序最初安装时,会附带由 Play 商店分发的 dex 元数据 (.dm) 文件,其中包含云配置文件。ART AOT 编译云配置文件中列出的方法。或者,如果应用程序在没有 dex 元数据文件的情况下安装,则不会执行 AOT 编译。
  2. 应用程序首次运行时,未进行 AOT 编译的方法将被解释执行。在解释执行的方法中,那些频繁执行的方法随后会被 JIT 编译。ART 会根据执行情况生成本地配置文件,并将其与云配置文件(如果存在)合并。
  3. 当设备处于空闲和充电状态时,编译守护程序会运行以基于首次运行期间生成的组合配置文件重新编译应用。
  4. 在后续的应用运行中,ART 会使用编译守护程序生成的工件,这些工件包含比首次运行期间生成的工件更多的 AOT 编译代码。未进行 AOT 编译的方法仍会被解释执行或 JIT 编译。ART 会根据执行情况更新配置文件安装,并且该配置文件随后会被编译守护程序的后续运行所获取。

ART 包括一个编译器(dex2oat 工具)和一个运行时库 (libart.so),该运行时库在启动期间加载。dex2oat 工具接受 APK 文件并生成运行时库加载的一个或多个编译工件文件。文件的数量、扩展名和名称可能会因版本而异,但截至 Android 8 版本,会生成以下文件

  • .vdex:包含一些额外的元数据以加速验证,有时还包含 APK 的未压缩 DEX 代码。
  • .odex:包含 APK 中方法的 AOT 编译代码。
  • .art (可选):包含 APK 中列出的某些字符串和类的 ART 内部表示形式,用于加速应用启动。

编译选项

ART 有两种编译选项类别

  1. 系统 ROM 配置:构建系统映像时 AOT 编译的代码。
  2. 运行时配置:ART 如何在设备上编译和运行应用。

编译器过滤器

配置这两个类别的核心 ART 选项之一是编译器过滤器。编译器过滤器驱动 ART 如何编译 DEX 代码,并且是传递给 dex2oat 工具的一个选项。从 Android 8 开始,有四个官方支持的过滤器

  • verify:仅运行 DEX 代码验证(不进行 AOT 编译)。
  • quicken:(Android 11 或更低版本)运行 DEX 代码验证并优化一些 DEX 指令以获得更好的解释器性能。
  • speed:运行 DEX 代码验证并 AOT 编译所有方法。不优化任何类的类加载。
  • speed-profile:运行 DEX 代码验证,AOT 编译配置文件中列出的方法,并优化配置文件中类的类加载。

系统 ROM 配置

预安装的库和应用在构建系统映像时进行 AOT 编译。此过程称为 dexpreopt。只要所有依赖项保持不变(尤其是启动类路径),此类编译文件就可用。

注意:如果设备采用 系统模块 更新,则启动类路径很可能在下次更新中发生更改,这将导致所有 dexpreopt 文件失效且不可用。

有许多 ART 构建选项可用于配置 dexpreopt。如何配置这些选项取决于系统映像的可用存储空间和预安装应用的数量。编译到系统 ROM 中的 JAR/APK 可以分为四类

  • 启动类路径代码:默认情况下使用 speed-profile 编译器过滤器进行编译。
  • 系统服务器代码(请参阅本文档后面的 PRODUCT_SYSTEM_SERVER_JARSPRODUCT_APEX_SYSTEM_SERVER_JARSPRODUCT_STANDALONE_SYSTEM_SERVER_JARSPRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS
    • (Android 14 及更高版本)默认情况下使用 speed-profile 编译器过滤器编译,如果未提供配置文件,则使用 speed 编译器过滤器编译。
    • (Android 13 及更低版本)默认情况下使用 speed 编译器过滤器编译。
    可通过 PRODUCT_SYSTEM_SERVER_COMPILER_FILTER 配置(请参阅本文档后面部分)。
  • 特定于产品的核心应用(请参阅本文档后面的 PRODUCT_DEXPREOPT_SPEED_APPS):默认情况下使用 speed 编译器过滤器进行编译。
  • 所有其他应用:默认情况下使用 speed-profile 编译器过滤器编译,如果未提供配置文件,则使用 verify 编译器过滤器编译。

    可通过 PRODUCT_DEX_PREOPT_DEFAULT_COMPILER_FILTER 配置(请参阅本文档后面部分)。

Makefile 选项

  • WITH_DEXPREOPT
  • 是否在系统映像上安装的 DEX 代码上调用 dex2oat。默认情况下启用。

  • DONT_DEXPREOPT_PREBUILTS(Android 5 及更高版本)
  • 启用 DONT_DEXPREOPT_PREBUILTS 可防止预构建文件进行 dexpreopt。这些应用在其 Android.mk 中指定了 include $(BUILD_PREBUILT)。跳过可能通过 Google Play 更新的预构建应用的 dexpreopt 可以节省系统映像中的空间,但会增加首次启动时间。请注意,此选项对 Android.bp 中定义的预构建应用没有影响。

  • PRODUCT_DEX_PREOPT_DEFAULT_COMPILER_FILTER(Android 9 及更高版本)
  • PRODUCT_DEX_PREOPT_DEFAULT_COMPILER_FILTER 指定 dexpreopt 应用的默认编译器过滤器。这些应用在 Android.bp 中定义,或者在其 Android.mk 中指定了 include $(BUILD_PREBUILT)。如果未指定,则默认值为 speed-profile;如果未指定该值且未提供配置文件,则默认值为 verify

  • WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY(自 Android 8 MR1 起)
  • 启用 WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY 仅对启动类路径和系统服务器 JAR 执行 dexpreopt。

  • LOCAL_DEX_PREOPT
  • 还可以通过在模块定义中指定 LOCAL_DEX_PREOPT 选项,在单个应用基础上启用或停用 Dexpreopt。这对于停用可能立即接收 Google Play 更新的应用的 dexpreopt 非常有用,因为更新会使系统映像中经过 dexpreopt 的代码过时。这对于节省主要版本升级 OTA 的空间也很有用,因为用户可能已经在数据分区中拥有较新版本的应用。

    LOCAL_DEX_PREOPT 支持值 truefalse 以分别启用或停用 dexpreopt。此外,如果 dexpreopt 不应从 APK 或 JAR 文件中剥离 classes.dex 文件,则可以指定 nostripping。通常,此文件会被剥离,因为它在 dexpreopt 之后不再需要,但最后一个选项对于允许第三方 APK 签名保持有效是必要的。

  • PRODUCT_DEX_PREOPT_BOOT_FLAGS
  • 将选项传递给 dex2oat 以控制启动映像的编译方式。它可以用于指定自定义映像类列表、编译类列表和编译器过滤器。

  • PRODUCT_DEX_PREOPT_DEFAULT_FLAGS
  • 将选项传递给 dex2oat 以控制除启动映像之外的所有内容的编译方式。

  • PRODUCT_DEX_PREOPT_MODULE_CONFIGS
  • 提供为特定模块和产品配置传递 dex2oat 选项的功能。它在产品的 device.mk 文件中通过 $(call add-product-dex-preopt-module-config,<modules>,<option>) 设置,其中 <modules> 是 JAR 和 APK 文件的 LOCAL_MODULELOCAL_PACKAGE 名称的列表。

  • PRODUCT_DEXPREOPT_SPEED_APPS(自 Android 8 起)
  • 已确定为产品核心且需要使用 speed 编译器过滤器编译的应用列表。例如,SystemUI 等持久性应用只有在下次重启时才有机会使用配置文件引导的编译,因此产品最好始终对这些应用进行 AOT 编译。

  • PRODUCT_SYSTEM_SERVER_APPS(自 Android 8 起)
  • 系统服务器加载的应用列表。默认情况下,这些应用使用 speed 编译器过滤器进行编译。

  • PRODUCT_ART_TARGET_INCLUDE_DEBUG_BUILD(自 Android 8 起)
  • 是否在设备上包含 ART 的调试版本。默认情况下,对于 userdebug 和 eng 版本,此选项已启用。可以通过显式将该选项设置为 truefalse 来覆盖此行为。

    默认情况下,设备使用非调试版本 (libart.so)。要切换,请将系统属性 persist.sys.dalvik.vm.lib.2 设置为 libartd.so

  • WITH_DEXPREOPT_PIC(直到 Android 7)
  • 在 Android 5.1.0 到 Android 6.0.1 中,可以指定 WITH_DEXPREOPT_PIC 以启用位置无关代码 (PIC)。这样,来自映像的编译代码不必从 /system 重新定位到 /data/dalvik-cache,从而节省数据分区中的空间。但是,由于它禁用了利用位置相关代码的优化,因此运行时性能会略有影响。通常,希望节省 /data 中空间的设备应启用 PIC 编译。

    在 Android 7.0 中,默认情况下启用了 PIC 编译。

  • WITH_DEXPREOPT_BOOT_IMG_ONLY(直到 Android 7 MR1)
  • 此选项已替换为 WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY,后者还对系统服务器 JAR 执行 preopt。

  • PRODUCT_SYSTEM_SERVER_COMPILER_FILTER
  • 此选项指定系统服务器的编译器过滤器。

    • (Android 14 及更高版本)如果未指定,则使用 speed-profile 编译器过滤器;如果未提供配置文件,则使用 speed 编译器过滤器。
    • (Android 13 及更低版本)如果未指定,则使用 speed 编译器过滤器。
    • 如果设置为 speed,则使用 speed 编译器过滤器。
    • 如果设置为 speed-profile,则使用 speed-profile 编译器过滤器;如果未提供配置文件,则使用 verify 编译器过滤器。
    • 如果设置为 verify,则使用 verify 编译器过滤器。

  • PRODUCT_SYSTEM_SERVER_JARSPRODUCT_APEX_SYSTEM_SERVER_JARSPRODUCT_STANDALONE_SYSTEM_SERVER_JARSPRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS
  • 以下是由系统服务器加载的 JAR 列表。JAR 使用 PRODUCT_SYSTEM_SERVER_COMPILER_FILTER 指定的编译器过滤器进行编译

    • (必需)PRODUCT_SYSTEM_SERVER_JARS:平台上的系统服务器类路径 JAR 列表(即,作为 SYSTEMSERVERCLASSPATH 的一部分)。必须将系统服务器类路径 JAR 添加到此列表。未能将系统服务器类路径 JAR 添加到列表中会导致这些 JAR 未被加载。
    • (必需)PRODUCT_APEX_SYSTEM_SERVER_JARS:随 APEX 交付的系统服务器类路径 JAR 列表(即,作为 SYSTEMSERVERCLASSPATH 的一部分)。格式为 <apex name>:<jar name>。必须将 APEX 系统服务器类路径 JAR 添加到此列表。未能将 APEX 系统服务器类路径 JAR 添加到列表中会导致这些 JAR 未被加载。
    • (可选,Android 13 及更低版本)PRODUCT_STANDALONE_SYSTEM_SERVER_JARS:系统服务器使用单独的类加载器动态加载的 JAR 列表(通过 SystemServiceManager.startServiceFromJar)。不强制要求将独立系统服务器 JAR 添加到此列表,但强烈建议这样做,因为它使 JAR 得到编译,从而获得良好的运行时性能。
    • (必需,自 Android 13 起)PRODUCT_APEX_STANDALONE_SYSTEM_SERVER_JARS:随 APEX 交付的 JAR 列表,系统服务器使用单独的类加载器动态加载这些 JAR(即,通过 SystemServiceManager.startServiceFromJar 或声明为 <apex-system-service>)。格式为 <apex name>:<jar name>。必须将独立 APEX 系统服务器 JAR 添加到此列表。未能将独立 APEX 系统服务器 JAR 添加到此列表中会导致启动失败。

    启动类路径配置

    预加载类列表是在启动时 Zygote 初始化的类列表。这可以节省每个应用单独运行这些类初始化程序的时间,从而使它们能够更快地启动并共享内存中的页面。预加载类列表文件默认位于 frameworks/base/config/preloaded-classes,它包含针对典型手机使用情况进行调整的列表。这对于可穿戴设备等其他设备可能有所不同,必须进行相应的调整。调整此列表时要小心;添加过多的类会在加载未使用的类时浪费内存。添加过少的类会强制每个应用都必须拥有自己的副本,这同样会浪费内存。

    用法示例(在产品的 device.mk 中)

    PRODUCT_COPY_FILES += <filename>:system/etc/preloaded-classes
    

    注意:您必须将此行放在继承任何从 build/target/product/base.mk 获取默认配置的产品配置 Makefile 之前。

    运行时配置

    JIT 选项

    以下选项仅影响 ART JIT 编译器可用的 Android 版本。

    • dalvik.vm.usejit:是否启用 JIT。
    • dalvik.vm.jitinitialsize(默认 64K):代码缓存的初始容量。代码缓存将定期进行 GC 并在需要时增加。
    • dalvik.vm.jitmaxsize(默认 64M):代码缓存的最大容量。
    • dalvik.vm.jitthreshold(默认 10000):方法的“热度”计数器需要超过的阈值,以便对该方法进行 JIT 编译。“热度”计数器是运行时库的内部指标。它包括调用次数、向后分支和其他因素。
    • dalvik.vm.usejitprofiles(直到 Android 13):是否启用 JIT 配置文件;即使 dalvik.vm.usejit 为 false,也可以使用此选项。请注意,如果此选项为 false,则编译器过滤器 speed-profile 不会 AOT 编译任何方法,并且等效于 verify。自 Android 14 起,JIT 配置文件始终处于启用状态,无法关闭。
    • dalvik.vm.jitprithreadweight(默认为 dalvik.vm.jitthreshold / 20):应用 UI 线程的 JIT“样本”(请参阅 jitthreshold)的权重。用于加速直接影响用户与应用交互体验的方法的编译。
    • dalvik.vm.jittransitionweight(默认为 dalvik.vm.jitthreshold / 10):在编译代码和解释器之间转换的方法调用的权重。这有助于确保编译涉及的方法以最大程度地减少转换(转换成本很高)。

    Dex2oat 选项

    这些选项影响设备上编译(也称为 dexopt),其中一些选项也影响 dexpreopt,而上面系统 ROM 配置部分中讨论的选项仅影响 dexpreopt。

    控制资源使用情况的选项

    • dalvik.vm.image-dex2oat-threads/dalvik.vm.image-dex2oat-cpu-set(直到 Android 11):用于启动映像的线程数和 CPU 核心集(见下文)。
    • dalvik.vm.boot-dex2oat-threads/dalvik.vm.boot-dex2oat-cpu-set
      • (直到 Android 11)用于启动时除启动映像之外的所有内容的线程数和 CPU 核心集(见下文)。
      • (自 Android 12 起)用于启动时所有内容(包括启动映像)的线程数和 CPU 核心集(见下文)。
        • 具体而言,自 Android 14 起,这对应于 ART Service 中的优先级类 PRIORITY_BOOT
    • dalvik.vm.restore-dex2oat-threads/dalvik.vm.restore-dex2oat-cpu-set
      • (自 Android 11 起,直到 Android 13)用于从云备份恢复的线程数和 CPU 核心集(见下文)。
      • (自 Android 14 起)用于比正常情况更延迟敏感的所有内容(包括从云备份恢复)的线程数和 CPU 核心集(见下文)。
        • 具体而言,这对应于 ART Service 中的优先级类 PRIORITY_INTERACTIVE_FAST
    • dalvik.vm.background-dex2oat-threads/ dalvik.vm.background-dex2oat-cpu-set(自 Android 14 起):在后台使用的线程数和 CPU 核心集(见下文)。
      • 具体而言,这对应于 ART Service 中的优先级类 PRIORITY_BACKGROUND
    • dalvik.vm.dex2oat-threads/dalvik.vm.dex2oat-cpu-set:用于其他所有内容的线程数和 CPU 核心集。

    CPU 核心集应指定为以逗号分隔的 CPU ID 列表。例如,要在 CPU 核心 0-3 上运行 dex2oat,请设置

    dalvik.vm.dex2oat-cpu-set=0,1,2,3
    

    设置 CPU 亲和性属性时,我们建议匹配 dex2oat 线程数的相应属性,以匹配所选 CPU 的数量,从而避免不必要的内存和 I/O 争用

    dalvik.vm.dex2oat-cpu-set=0,1,2,3
    dalvik.vm.dex2oat-threads=4
    

    除了上述系统属性外,您还可以使用任务配置文件来控制 dex2oat 的资源使用情况(请参阅 Cgroup 抽象层)。

    支持的任务配置文件为

    • Dex2OatBackground(自 Android 14 起)(默认情况下继承 Dex2OatBootComplete):控制在后台使用的资源。
      • 具体而言,这对应于 ART Service 中的优先级类 PRIORITY_BACKGROUND
    • Dex2OatBootComplete:
      • (直到 Android 13)控制启动后用于所有内容的资源。
      • (自 Android 14 起)控制启动后用于所有内容(不在后台)的资源。
        • 具体而言,这对应于 ART Service 中的优先级类 PRIORITY_INTERACTIVE_FASTPRIORITY_INTERACTIVE

    当同时指定系统属性和任务配置文件时,两者均生效。

    控制堆大小的选项

    • dalvik.vm.image-dex2oat-Xms:启动映像的初始堆大小。
    • dalvik.vm.image-dex2oat-Xmx:启动映像的最大堆大小。
    • dalvik.vm.dex2oat-Xms:其他所有内容的初始堆大小。
    • dalvik.vm.dex2oat-Xmx:其他所有内容的最大堆大小。

    不应减小控制 dex2oat 的初始和最大堆大小的选项,因为它们可能会限制可以编译的应用。

    控制编译器过滤器的选项

    • dalvik.vm.image-dex2oat-filter(直到 Android 11):启动映像的编译器过滤器。自 Android 12 起,启动映像的编译器过滤器始终为 speed-profile,并且无法更改。
    • dalvik.vm.systemservercompilerfilter(自 Android 13 起):系统服务器的编译器过滤器。请参阅 PRODUCT_SYSTEM_SERVER_COMPILER_FILTER
    • dalvik.vm.systemuicompilerfilter(自 Android 13 起):System UI 软件包的编译器过滤器。
    • dalvik.vm.dex2oat-filter(直到 Android 6):其他所有内容的编译器过滤器。
    • pm.dexopt.<reason>(自 Android 7 起):其他所有内容的编译器过滤器。有关 Android 14 及更高版本,请参阅 ART Service 配置;有关 Android 13 及更低版本,请参阅 软件包管理器配置

    控制除启动映像之外的所有内容的编译的其他选项

    • dalvik.vm.dex2oat-very-large(自 Android 7.1 起):禁用 AOT 编译的最小总 dex 文件大小(以字节为单位)。
    • dalvik.vm.dex2oat-swap(自 Android 7.1 起)(默认值:true):允许为 dex2oat 使用交换文件。这可以帮助避免内存不足崩溃。请注意,即使此选项已打开,dex2oat 也仅在某些条件下(例如,当 dex 文件数量很大时)使用交换文件,并且条件可能会更改。
    • dalvik.vm.ps-min-first-save-ms(自 Android 12 起):运行时库在首次启动应用时生成应用配置文件的最少等待时间。
    • dalvik.vm.ps-min-save-period-ms(自 Android 12 起):更新应用配置文件的最少等待时间。
    • dalvik.vm.dex2oat64.enabled(自 Android 11 起)(默认值:false):是否使用 64 位版本的 dex2oat。
    • dalvik.vm.bgdexopt.new-classes-percent(自 Android 12 起)(默认值:20):配置文件中触发重新编译的新类的最小百分比(介于 0 到 100 之间)。仅适用于配置文件引导的编译 (speed-profile),通常在后台 dexopt 期间。请注意,除了百分比阈值之外,还有至少 50 个新类的阈值,并且它是不可配置的。
    • dalvik.vm.bgdexopt.new-methods-percent(自 Android 12 起)(默认值:20):配置文件中触发重新编译的新方法的最小百分比(介于 0 到 100 之间)。仅适用于配置文件引导的编译 (speed-profile),通常在后台 dexopt 期间。请注意,除了百分比阈值之外,还有至少 100 个新方法的阈值,并且它是不可配置的。
    • dalvik.vm.dex2oat-max-image-block-size(自 Android 10 起)(默认值:524288):压缩映像的最大实体块大小。大型映像被拆分为一组实体块,以便没有块大于最大大小。
    • dalvik.vm.dex2oat-resolve-startup-strings(自 Android 10 起)(默认值:true):如果为 true,则使 dex2oat 解析从配置文件中标记为“启动”的方法引用的所有 const-string。
    • debug.generate-debug-info(默认值:false):是否为本机调试生成调试信息,例如堆栈展开信息、ELF 符号和 dwarf 段。
    • dalvik.vm.dex2oat-minidebuginfo(自 Android 9 起)(默认值:true):是否生成打印回溯所需的最少量 LZMA 压缩调试信息。

    ART Service 选项

    自 Android 14 起,应用的设备上 AOT 编译(也称为 dexopt)由 ART Service 处理。有关配置 ART Service 的信息,请参阅 ART Service 配置

    软件包管理器选项

    在 Android 14 之前,应用的设备上 AOT 编译(也称为 dexopt)由软件包管理器处理。有关配置软件包管理器以进行 dexopt 的信息,请参阅 软件包管理器配置

    A/B 特定配置

    ROM 配置

    从 Android 7.0 开始,设备可以使用两个系统分区来启用 A/B 系统更新。为了节省系统分区大小,预先优化的文件可以安装在未使用的第二个系统分区中。然后在首次启动时将它们复制到数据分区。

    用法示例(在 device-common.mk 中)

    PRODUCT_PACKAGES += \
         cppreopts.sh
    PRODUCT_PROPERTY_OVERRIDES += \
         ro.cp_system_other_odex=1
    

    以及在设备的 BoardConfig.mk

    BOARD_USES_SYSTEM_OTHER_ODEX := true
    

    请注意,启动类路径代码、系统服务器代码和特定于产品的核心应用始终编译到系统分区。默认情况下,所有其他应用都编译到未使用的第二个系统分区。这可以使用 SYSTEM_OTHER_ODEX_FILTER 进行控制,该选项的默认值为

    SYSTEM_OTHER_ODEX_FILTER ?= app/% priv-app/%
    

    后台 OTA dexopt

    在启用 A/B 的设备上,可以在使用新的系统映像重新启动之前在后台编译应用程序。请参阅 后台应用编译,以选择性地在系统映像中包含编译脚本和二进制文件。用于此编译的编译过滤器由以下选项控制

    pm.dexopt.ab-ota=speed-profile
    

    我们建议使用 speed-profile 以利用配置文件引导的编译并节省存储空间。

    JDWP 选项

    用户调试版本中 Java 调试线协议 (JDWP) 线程的创建通过 persist.debug.dalvik.vm.jdwp.enabled 系统属性进行控制。默认情况下,未设置此属性,并且仅为可调试应用创建 JDWP 线程。要为可调试应用和不可调试应用都启用 JDWP 线程,请将 persist.debug.dalvik.vm.jdwp.enabled 设置为 1。必须重新启动设备才能使属性更改生效。

    要在用户调试版本上调试不可调试的应用,请运行以下命令以启用 JDWP

      adb shell setprop persist.debug.dalvik.vm.jdwp.enabled 1
      adb reboot
      
    对于运行 Android 13 及更低版本的设备,运行时库会为用户调试版本上的可调试应用和不可调试应用创建 JDWP 线程。这意味着可以在用户调试版本上附加调试器或分析任何应用。