Why Standard Java Fails on Battery Power
Building a mobile operating system presents a brutal constraint: memory is scarce and batteries drain quickly. Early Android devices shipped with painfully small amounts of RAM. Google needed a virtual machine that could execute app code without immediately killing the phone. Using a standard Java Virtual Machine (JVM) was impossible. A traditional JVM relies on a stack-based architecture, which pushes and pops variables continuously. This requires multiple instructions just to perform basic math, demanding more memory and CPU cycles than early mobile hardware could afford.
The solution was a completely custom engine called the Dalvik Virtual Machine. Instead of a stack, Dalvik used a register-based architecture. A register machine can compute a = b + c in a single instruction rather than four, significantly reducing the memory footprint. To run on Dalvik, build tools converted standard Java .class files into a highly compressed Dalvik Executable (.dex) format.
When a user tapped an app icon, Dalvik read the .dex file and used a Just-In-Time (JIT) compiler to translate the bytecode into native CPU instructions on the fly. JIT compilation functions like a live interpreter translating a speech sentence by sentence. The app starts quickly, but the interpreter works constantly while the app is running.
Did You Know: Android does not actually run standard Java bytecode. While developers write Java or Kotlin code, the Android build pipeline aggressively cross-compiles it into the distinct DEX format before the app ever touches a device.
Dalvik saved precious memory through its register design and DEX format, but constantly translating code on the fly at runtime was destroying something even more critical: the battery.
The Great Install-Time Tradeoff: Pure AOT
Burning CPU cycles every time a user scrolls a list destroys battery life. As apps grew more complex, the Dalvik JIT compiler simply could not keep up, causing severe UI stutter. The only way to stop translating code at runtime is to translate it all before the app ever runs. Google realized they had to shift the heavy lifting from the user's active session to the installation phase.
In Android 5.0 (Lollipop), Google completely removed Dalvik and introduced the Android Runtime (ART). ART fundamentally changed how apps execute by implementing pure Ahead-of-Time (AOT) compilation. When a user downloaded an APK, a system tool called dex2oat woke up and aggressively compiled every single line of DEX bytecode into a massive native ELF binary before the app was ever allowed to open.
Because the code was already compiled to native machine language, runtime CPU overhead dropped to zero. The battery drain stopped. But this "perfect" solution created an immediate disaster. A massive game that used to install in five seconds now took five minutes. System updates forced the device to recompile every single app on the phone, leading to the infamous 30-minute "Android is upgrading" lock screens. Furthermore, the compiled native binaries consumed massive amounts of storage space on devices that often only had 8GB of flash storage.
Warning: ART does not mean Android executes pure C++ code without a runtime. While the execution is native, ART still heavily manages garbage collection, threading, and memory limits in the background.
AOT completely solved the runtime battery drain, but it made app installations painfully slow and caused app sizes to explode—forcing Android to search for a compromise between JIT and AOT.
The Hybrid Sweet Spot: Profile-Guided JIT
Users hated waiting for apps to compile. To fix the installation bottleneck without sacrificing runtime performance, Android 7.0 introduced a hybrid architecture. Google brought JIT compilation back, but relegated it to a transitional role. The goal was to only compile the code the user actually executed, rather than blindly compiling everything.
When an app installs on a modern Android device, it installs instantly with zero AOT compilation. The first time the user opens the app, ART uses the JIT compiler. As the user navigates, ART quietly profiles their behavior, marking which methods are executed frequently (hot paths). When the user goes to sleep and plugs the phone into a charger, a background service called dexopt wakes up, reads that profile, and performs AOT compilation solely on those specific hot paths.
This is Profile-Guided Optimization (PGO) in action. If you open a heavy app on day one, scrolling might stutter slightly as the JIT compiler works. But by day two, the exact paths you use most often are pure native binaries, ensuring buttery smooth performance without wasting storage on features you never tap.
Common Mistake: Many engineers assume JIT died permanently when ART was introduced. JIT actually made a massive comeback in Android 7.0, serving as the critical profiling engine that makes modern AOT compilation targeted and efficient.
While local profiling solved the storage and install-time issues, a user still had to endure a suboptimal "Day 1" experience—unless their phone could somehow borrow someone else's profile.
Cloud Profiles: Crowdsourcing App Performance
Local profiling relies on the user suffering through JIT overhead once before the system optimizes the app. But why should millions of users individually profile the exact same login screen? The most frequent paths through an app are largely universal. Google realized they could aggregate this data to eliminate the cold-start penalty entirely.
In Android 9.0, Google introduced Cloud Profiles. When early adopters download a new app update, their devices generate local profiles as they navigate the UI. Google Play securely uploads these anonymous profiles to the cloud. Google aggregates them into a master map of the app's hot paths. When a new user downloads that app later, Google Play bundles this map alongside the APK.
This completely bypasses the JIT phase. Before the new user even taps the app icon for the first time, their local dexopt daemon uses the cloud profile to natively compile the critical startup paths. The app achieves peak Day-5 performance on the very first second it launches.
Tip: Cloud profiles do not mean apps are executed or compiled in the cloud. Compilation always happens 100% locally on the device's CPU. The cloud only delivers the blueprint of what to compile.
With an execution environment highly optimized for battery, storage, and speed, Android can now efficiently execute complex application logic, but that logic is completely isolated from the physical hardware it needs to control.
We now have a blisteringly fast runtime engine, but an engine cannot steer the car. To actually access the camera, Bluetooth, and GPU, our compiled code must cross the platform's most complex boundary yet—Project Treble and the Hardware Abstraction Layer.