In this article, we will talk about the Android runtime environment. Notably, we promise to be brief and explain in short ART and Dalvik (DVM) differences in Android.
Let us clear up the difference between JVM and DVM first.
Java Virtual Machine is a virtual machine capable of executing Java bytecode regardless of the base platform. It is based on the principle "Write once, run anywhere." The Java bytecode can run on any machine capable of supporting JVM.
The Java compiler converts .java files into class files (bytecode). The bytecode is passed to JVM, which compiles it into machine code for execution directly on the CPU.
- Stack architecture. The stack is used as a data structure where methods are placed and stored. It works by the LIFO scheme or "Last in - First Out."
- Runs .class files only.
- Uses a JIT compiler.
Dalvik Virtual Machine (DVM) is a Java virtual machine developed and written by Dan Bornstein and others as part of the Android mobile platform.
We can say that Dalvik is a runtime for Android operating system components and user applications. Each process is executed in its isolated domain. When a user starts an app (or the operating system launches one of its components), the Dalvik virtual machine kernel (Zygote Dalvik VM) creates a separate, secure process in shared memory, where VM is directly deployed as the environment to run the application. In short, Android within looks like a set of Dalvik virtual machines, with executing an app in each.
- Register-based architecture. The data structure with methods placed there is based on processor registers. Due to POP and PUSH absence, instructions in register VM are executed faster than similar ones in stack VM
- Executes the bytecode of its format. Android dexer (we'll talk about it later) converts .class files into .dex format optimized for DVM execution. Unlike a .class file, a .dex file contains several classes at once.
If you are interested, you can read more about DVM architecture here.
A crucial step in creating an APK is converting the Java bytecode to .dex bytecode for Android Runtime and Android developers to know about it. The dex compiler mainly works "undercover" in routine application development, but it directly affects the application build time, .dex file size, and runtime performance.
As already mentioned, the .dex file itself contains several classes at once. Repeating strings and other constants, used in multiple .class files, are included only to save space. Java bytecode is also converted to an alternative command set used by DVM. An uncompressed .dex file is usually a few percent smaller than a compressed Java archive (JAR) from the same .class files.
Initially, .class files were converted to .dex using the built-in DX compiler. But starting from Android Studio 3.1 onwards, the default compiler was D8. Compared to the DX compiler, the D8 compiles faster and outputs smaller .dex files, providing high application performance during runtime. The resulting bytecode is minified using an open-source utility ProGuard. As a result, we get the same .dex file, but smaller. Then this file is used for APK building and finally for deploying it on the Android device.
When working with Android Studio 3.4 and Android Gradle 3.4.0 plugin or higher, Proguard is no longer used for code optimization during compilation. The plugin works by default with R8 instead, which performs code shrinking, optimization, and obfuscation itself. Although R8 offers only a subset of functions provided by Proguard, it allows the converting Java bytecode to dex bytecode to be performed once, further reducing the build time.
We all know that most applications use third-party libraries such as Guava, Jetpack, Gson, Google Play Services. When we use one of these libraries, often only a small part of each library is used in an application. Without code shrinking, the entire code of the library is stored in your app.
It happens when developers use verbose code to improve readability and maintainability. For example, meaningful variable names and builder pattern can be used to make it easier for others to understand your code. But such patterns usually result in more code than is needed.
In this case, R8 comes to the rescue. It allows you to significantly reduce the application's size by optimizing the volume of code actually used by the app.
And below you can see the R8 effectiveness when the beta version was presented (taken from the Android Developers Blog source):
For detailed information, check the official documentation and the report mentioned above.
DVM was explicitly designed for mobile devices and was used as a virtual machine to run android apps up until Android 4.4 Kitkat.
Starting from this version, ART was introduced as a runtime environment, and in Android 5.0 (Lollipop), ART completely replaced Dalvik.
The main visible difference between ART and DVM is that ART uses AOT compilation, while DVM - JIT compilation. Not so long ago, ART started using a hybrid of AOT and JIT. We'll take a look at this a little bit further.
- Uses JIT Compilation: whenever you start an application, the part of the code necessary for app execution compiles. The rest of the code compiles dynamically. This slows down app launch and operation but reduces the installation time.
- Speeds up the device's startup because the application cache is created at runtime.
- DVM apps require less memory than those running on ART.
- Reduces battery performance by increasing the CPU load.
- Dalvik is "obsolete" and is not used on Android versions above 4.4.
- Uses AOT compilation, i.e., compiles all code during the installation of the app. This speeds up apps running and operation but requires more installation time.
- Slows the device's startup because the cache is created during the first boot.
- Due to the AOT compilation approach, requires more memory compared to DVM applications.
- Increases battery performance by reducing CPU operation due to the compilation absence when running applications.
- Improved Garbage Collection. When using Dalvik, garbage collectors had to perform two heap passes, which resulted in bad UX. In the case of ART, this is not the case: it cleans the heap once for memory consolidation
Since Android 7, the Android Runtime Environment includes a JIT compiler with code profiling. The JIT compiler complements the AOT compiler, improves runtime performance, saves disk space, and accelerates app and system updates.
It's carried out according to the following scheme:
Instead of running the AOT compilation of each application during the installation, it runs the application under a VM using the JIT compiler (almost the same as in Android < 5.0) but keeping track of pieces of app code executing most often. This information is after used for the AOT compilation of these code fragments. The last operation is performed only during the smartphone is inactive, which is on a charge.
Merely speaking, now two different approaches work together, which brings its benefits:
- More efficient compilation - when you start an app, the compiler can learn much more about its operation than when performing static analysis, and, as a result, more suitable optimization methods are applied for each situation.
- Preserving RAM and permanent memory - bytecode is more compact than machine code. When we perform AOT compilation only of separate application parts and do not compile applications the user doesn't use, we can significantly save. NAND-memory space;
- A sharp increase in installation speed and first boot after system update - no AOT compilation, no delay.
More information about JIT compiler implementation in ART you can find here.
In this article, we have analyzed the main differences between DVM and ART, and generally looked at how Android improved its development tools over time.
ART is still under development: new features are being added to improve the experience for both users and developers.
We hope this article will be helpful for those who are just getting started with Android.