omeraydin.dev
January 17, 2024

Does Android run Java? (Spoiler: No) - A deep dive into the Android build process

As the title suggests, the answer is no, but perhaps we first need to define what "running Java" means. For me, it means running a Java program in an actual Java Virtual Machine. And by that definition, we should already be able to debunk the biggest misconception about Android -- that it runs Java. It does not!

Android Runtime

Android uses a special runtime called Android Runtime (or ART) which doesn't run Java programs (or Java bytecode), instead it runs Dalvik bytecode or as they're more commonly called, Dalvik executables (DEX). The distinction is important because both the Java bytecode as a format and the JVM are quite different from the DEX format and the ART.

JVM is a more general purpose, stack-based virtual machine that usually utilizes Just-In-Time (JIT) compilation for performance. ART is more specifically tailored for Android devices, is register based, and uses Ahead-Of-Time (AOT) compilation for performance. We'll discuss what those mean in a second.

First let's address the elephant in the room. One might think "Don't Android developers write Java to create Android apps? So the logical output of that would be.. that Android runs Java, surely?" It's an honest mistake, but to understand the real confusion, we need to dive a little deep into the Android build process.

The Android Build Process

For simplicity purposes, I've left out many parts of the build process in the diagram, but hopefully that shouldn't be a huge problem. As seen in the diagram, Java code is first compiled to Java bytecode using javac, then the resulting JAR is compiled to DEX files before being packaged up to the final APK file. Android doesn't understand the Java bytecode format so we have to do the conversion step.

This JAR to DEX conversion used to be done by a tool called dx but it later got deprecated to be replaced by D8 instead.

But why? Why can't Android just run JVM and not need the extra build step? Well there are multiple reasons, but for starters ART/formerly DVM tries to be much more performant and efficient (in terms of both battery and memory) for use in Android devices. JVM is a more general-purpose virtual machine that tries to be suitable to be run in a wide variety of devices, but Android needed a more custom solution.

Ahead-Of-Time (AOT) Compilation

One of the big optimizations ART brings to Android is Ahead-Of-Time (AOT) compilation.

The way AOT works is: when you install an APK file, Android runs dex2oat at install time (ahead of time if you will) to compile the DEX file to an OAT file, a native ELF binary which can be directly executed by the device's processor. This way, essentially all the interpreting that would have to be carried out by ART is skipped, and we get better performance in Android apps in exchange for some increase in install time. I think the tradeoff is worth it.

The OAT file that is generated by dex2oat stands for Of Ahead Time, it's not a typo of AOT :-)

In contrast, Just-In-Time (JIT) compilation -the one used by JVM- works by converting bytecode to native machine code at runtime. Hence its name. It analyzes your code in very clever ways to detect and compile specific methods to machine code to maximize performance. I'm not going to go into too much detail, however the reason it's done at runtime is because certain information like the usage frequency of the methods are only available at runtime.

All in all, this is not to say Android doesn't use Just-In-Time (JIT) compiling at all. Prior to Android 5, Android used the Dalvik Virtual Machine (DVM) instead of the current ART. DVM was JIT oriented and therefore had faster install times but worse performance. ART brought AOT compiling to the table, but even now, Android doesn't only use AOT. Starting in Android 7, ART started using a hybrid solution that combines ART and JIT for the best of both worlds (see here). It's quite the rabbit hole!

Why all the indirection? Why can't we just convert Java directly to DEX?

Well... that's actually possible and it has been tried before. The Android team experimented with a new tool chain called Jack and Jill, which aimed to compile Java source code directly into DEX files, while even handling things like shrinking, obfuscation etc. out of the box, all in a single tool.

A diagram showing how the toolchain works, straight from the official docs of Jack and Jill

This sounded great on paper, but it wasn't long before they realized this actually makes certain migrations really painful if not impossible. All the existing Java-bytecode-based tools, such as annotation processors, bytecode rewriter tools, plugins i.e a huge ecosystem of libraries would either have to be all rewritten/integrated for Jack and Jill, or users would have to give those up. This certainly was less than ideal and thus Android decided to ditch this idea completely.


TL;DR: We compile Java code into Java bytecode, then that into Dalvik bytecode, which then Android runs in ART for extra optimization. There's no JVM running on Android!

In the next article, I want to dive more into the history of Dx, ProGuard, D8/R8 and how it all ties together. Stay tuned!

In
,
,
,