Zygote Fork Model and Copy-on-Write (COW)
The Zygote process is the progenitor of all Android applications. It initializes the Android Runtime (ART), preloads common framework classes and resources, and then enters a socket loop waiting for commands from the ActivityManagerService to launch new applications.
When a new application is requested, Zygote does not boot a new virtual machine from scratch. Instead, it utilizes the fork() system call.
The Power of fork() and COW
The fork() mechanism creates a child process that is an exact duplicate of the parent process. In modern Linux systems, this duplication does not immediately copy all memory pages. Instead, it relies on a mechanism known as Copy-on-Write (COW).
- Shared Memory: Both Zygote and the newly spawned application process initially share the same physical memory pages containing the preloaded classes, resources, and ART state.
- Private Copies on Modification: If either the parent or the child attempts to write to a shared memory page, a page fault occurs. The kernel then creates a private copy of that page for the writing process.
This architecture has two massive benefits:
- Rapid Startup: Launching an app is practically instantaneous because the heavy lifting of initializing the runtime and loading core classes is already done.
- Memory Efficiency: Since most preloaded framework classes are read-only, their memory is shared across all running applications, dramatically reducing the overall memory footprint of the system.
// simplified conceptual representation from platform/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
pid_t pid = fork();
if (pid == 0) {
// Child process (The new app)
// Execute app-specific initialization
SetGids();
SetCapabilities();
// Drop root privileges and start the app's main loop
} else if (pid > 0) {
// Parent process (Zygote)
// Continue listening for new fork requests
}
ART Compilation Pipeline: From Install to Execution
The Android Runtime (ART) has evolved significantly. Modern Android uses a hybrid compilation model combining Ahead-of-Time (AOT), Just-in-Time (JIT) compilation, and Profile-Guided Optimization (PGO).
1. Installation: DEX to OAT (dex2oat)
When an APK is installed, the dex2oat tool runs. Historically, dex2oat aggressively compiled all DEX code into native machine code (OAT file format, which is essentially an ELF file). However, this led to slow install times and large storage footprints.
Modern ART (Android 7.0+) restricts initial AOT compilation. Apps are installed quickly without full AOT compilation.
2. Execution: Interpreter and JIT
When the app is launched, ART initially executes the code using a highly optimized interpreter. As the app runs, the runtime profiles the execution to identify "hot" methods: methods that are called frequently.
Once a method crosses a specific execution threshold, it is handed off to the JIT compiler. The JIT compiler compiles this method into native code asynchronously.
3. Profile-Guided Optimization (PGO) and Background AOT
As the app is used, ART continues to record which methods are executed and which classes are loaded. This information is saved into a profile file (/data/misc/profiles/cur/0/pkg.name/primary.prof).
When the device is idle and charging, the BackgroundDexOptService triggers dex2oat again. This time, dex2oat acts as a profile-guided AOT compiler. It reads the profile generated by the JIT and compiles only the hot methods into native code.
Garbage Collection (GC) Pause Analysis
GC pauses, also known as "stop-the-world" pauses, occur when the runtime must suspend application threads to manage memory. Frequent or lengthy pauses result in UI jank.
ART uses a concurrent copying garbage collector. It has several phases, but the critical metric is the pause time.
Analyzing GC with logcat
You can observe GC behavior directly in logcat:
adb logcat -s art
You might see log lines similar to:
I art: Background concurrent copying GC freed 1234(56KB) AllocSpace objects, 0(0B) LOS objects, 40% free, 10MB/17MB, paused 1.2ms total 10.5ms
- freed: The amount of memory reclaimed.
- paused: The duration application threads were suspended.
- total: The total time the GC cycle took (including concurrent background work).
Identifying Allocation Hotspots
To reduce GC pressure, you must minimize object allocations in performance-critical paths (like onDraw or tight loops). Use the Android Studio Memory Profiler to track allocations and identify which methods are spawning excessive objects.
JIT Profiles and Cloud Profiles
To eliminate the "warm-up" period where an app relies on the interpreter before JIT kicks in, Android introduced Cloud Profiles.
When users interact with your app, their generated profiles are aggregated by Google Play. Play then creates a "baseline profile" representing common usage patterns.
When a new user downloads your app, Play delivers this baseline profile alongside the APK. dex2oat uses this profile during installation to perform targeted AOT compilation immediately. This means the app starts fast and runs smoothly from the very first launch, bypassing the initial interpreter phase for critical paths.