Advanced AOSP Subsystems
3 min read

AOT Compilation

Overview

This lesson covers AOT Compilation (Ahead-Of-Time) within the Android Runtime (ART). AOT compilation translates DEX bytecode into native machine code before the application is executed, shifting the compilation overhead from runtime to install time or idle time.

The dex2oat Tool

The heart of ART's compilation pipeline is the dex2oat compiler. It is a standalone executable on the device responsible for taking .dex files as input and producing optimized .oat (ELF), .vdex, and .art (boot image) files.

dex2oat is highly configurable, accepting numerous flags to control the level of optimization, the target instruction set, and the memory constraints during compilation.

# Example of manually invoking dex2oat (simplified)
adb shell dex2oat \
  --dex-file=/data/app/com.example.app/base.apk \
  --oat-file=/data/app/com.example.app/oat/arm64/base.odex \
  --compiler-filter=speed-profile \
  --profile-file=/data/misc/profiles/cur/0/com.example.app/primary.prof \
  --instruction-set=arm64

AOT Compilation Strategies

ART's approach to AOT compilation has evolved significantly to balance CPU usage, storage space, and app startup speed.

AOT Compilation During Install

In older Android versions (5.0 to 6.0), dex2oat compiled the entire application during installation. While this provided excellent runtime performance, it led to notoriously long install times and consumed excessive storage space.

Modern Android versions (7.0+) use a more nuanced approach. During installation, the system typically performs minimal compilation. It extracts the DEX files, runs verification (generating a VDEX file), and may compile only a small portion of the code or rely entirely on the interpreter/JIT for the first few runs.

AOT Compilation at Idle (Background Dexopt)

To optimize performance without blocking the user, Android relies on a background service called background_dexopt (managed by JobScheduler).

When the device is:

  1. Idle (screen off, not actively used)
  2. Connected to power (charging)

The background job wakes up and invokes dex2oat to compile applications. Crucially, it uses the JIT profiles gathered during actual usage to compile only the "hot" methods, saving storage space while maximizing performance where it matters.

Compilation Filters

The behavior of dex2oat is heavily governed by the compilation filter. These filters dictate exactly how much of the DEX code should be translated to native machine code.

You can check the current compilation status of an app using dumpsys:

adb shell dumpsys package com.example.app | grep "compilation_filter"

Common compilation filters include:

  • verify: Does not generate any native code. It only runs bytecode verification and generates a .vdex file. Execution will rely entirely on the interpreter and JIT.
  • quicken: (Deprecated in newer Android versions) Performs verification and replaces certain DEX instructions with faster, optimized equivalents (e.g., resolving field offsets). Does not generate native machine code.
  • space: Compiles code to native machine code, but prioritizes minimizing the size of the resulting OAT file over execution speed.
  • speed: Aggressively compiles all methods to native machine code to maximize execution speed. This results in the largest OAT files and longest compilation times.
  • speed-profile: The default and most common filter in modern Android. It compiles only the methods listed in the provided profile file (gathered via JIT or Play Store Cloud Profiles) to native code.
  • everything: Compiles absolutely everything, including methods that would normally be skipped (like class initializers). Rarely used except for debugging or specific system components.