333 Android Zygote:所有 APP 进程的孵化器
系列导航:330-启动总览 | 331-Bootloader | 332-init进程
上篇我们讲了 init 进程——用户态第一个进程,负责解析配置、挂载文件系统、管理属性、启动关键服务。其中最重要的一个服务,就是 Zygote。
Zygote 是 Android 最独特的设计之一。它是所有 Java 应用进程的父进程,通过预加载类库和 fork 模型,实现了惊人的应用启动速度优化。
理解 Zygote,是理解 Android 整体架构的钥匙。
Zygote 是什么
Zygote(受精卵)——这个名字已经暗示了它的作用:它是 Android 系统中所有进程的”母体”。
从进程关系上看:
init (PID 1)
└── zygote (PID 1 的子进程)
├── system_server (zygote fork 出来)
└── com.example.myapp (zygote fork 出来,100+ 个 APP 进程)从技术角度说,Zygote 是一个 Java 进程,它在启动时:
- 预加载所有 Java 核心类库(android.jar + framework.jar)
- 预创建一个虚拟机的实例(但还没启动 APP 的 main)
- 启动一个 ServerSocket,等待 AMS 发请求
- 收到 fork 请求后,通过
fork()系统调用复制自己
Zygote 进程(已加载所有类库和资源)
│
│ AMS 请求 fork 新 APP 进程
▼
fork() ─────────────────────────────────────────┐
│ │
▼ ▼
子进程(APP) 父进程(继续监听)
· 复制父进程地址空间
· 继承所有已加载的类
· 执行 Application.onCreate()
· 启动 ActivityThread.main()关键点:因为类已经在 Zygote 里加载过了,子进程不用重新加载类,节省了数秒钟的冷启动时间。
为什么需要 Zygote
Android 在 Dalvik 时代引入 Zygote,主要解决两个问题:
问题 1:每个进程要加载所有类 → 慢
在没有 Zygote 的设计里,每个 APP 进程都需要:
APP 进程启动
→ 创建一个 Dalvik/ART 虚拟机实例(~20-50MB 内存分配)
→ 加载 java.*, javax.*, android.*, dalvik.*, ...
→ 链接 native 库(JNI)
→ 执行 Application.onCreate()
→ 启动四大组件这会导致:
- 首次启动慢:每次都要重新加载几千个类
- 内存浪费:每个进程都有自己的一份类数据(.dex 缓存)
问题 2:Native 库初始化开销
JNI 调用 native 代码时,需要 System.loadLibrary()。如果每个 APP 都加载同一套 so,内存中会有多份拷贝。
┌────────────────┬────────────────┐
│ Zygote (共享) │ APP1 进程 │
│ libandroid.so │ - │ ← Zygote 已加载,fork 后共享
│ libicu*.so │ - │
└────────────────┴────────────────┘Zygote 通过 fork + copy-on-write 机制解决这两个问题:
- fork 后子进程共享父进程已加载的类和内存(直到写了新数据才复制)
- 每个 APP 进程只需要极少的启动时间
Zygote 的启动流程
Zygote 由 init 启动,对应配置 init.zygote64_32.rc(或 init.zygote64_64.rc):
service zygote /system/bin/app_process64
class main
socket zygote stream 660 root system
socket usap_pool_min_streaming_native_pool stream 660 root system
onexec /system/bin/linker64 /system/bin/app_process64
priority 1000
priority -20
limit 16384 1000000 1000000 10 1000000
group root readproc reserved_disk
seclabel u:object_r:zygote_exec:s01. app_process_main 入口
/system/bin/app_process64 是 Zygote 的入口程序(app_main.cpp):
int app_main(int argc, char* const argv[]) {
// 解析命令行参数
AppRuntime runtime;
// 创建 AppRuntime(继承 AndroidRuntime)
// 调用 start() 启动 Java 虚拟机
runtime.start("com.android.internal.os.ZygoteInit", ...);
}2. AndroidRuntime 启动 JVM
// frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, ...) {
// 1. 创建 JVM
JNI_CreateJavaVM(&mJavaVM, &mEnv, &opts);
// 2. 注册 JNI 方法(给 Java 层调用的 native 方法)
REGISTER__JNI_METHODS(mEnv);
// 3. 加载核心 Framework 类
jclass clazz = mEnv->FindClass(className); // "com.android.internal.os.ZygoteInit"
mEnv->CallStaticVoidMethod(clazz, methodId, ...);
}3. ZygoteInit.main(Java 层)
// frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String[] argv[]) {
// 1. 预加载资源(类、drawable、theme、打开的文件描述符)
preload();
// 2. 启用 class preloading(Android 9+)
maybeEnableHeapRobustness();
// 3. 注册 Zygote Socket(等待 AMS 发请求)
registerZygoteSocket();
// 4. 启动 System Server(核心系统服务)
preLoadAndStartSystemServer();
// 5. 进入循环,等待 AMS fork 请求
caller = zygoteServer.runSelectLoop();
}预加载(Preload)
ZygoteInit.main() 的第一步是 preload()。这是 Android 冷启动最耗时的阶段之一。
预加载的完整流程
┌───────────────────────────────────────────────────────────────┐
│ Zygote preload() │
├───────────────────────────────────────────────────────────────┤
│ 1. preloadClasses() │
│ ・加载 ~1500-2000 个核心类 │
│ ・来自 framework.jar、core-libart.jar │
│ ・把类写入 JVM 内部的 class table(zygote 进程地址空间) │
│ │
│ 2. preloadResources() │
│ ・加载 drawable、layout、values 资源 │
│ ・资源表 ResourceTable 全量加载到内存 │
│ ・Zygote 会预打开 100+ 个 APK 的资源 │
│ │
│ 3. preloadOpenGL() │
│ ・加载 OpenGL ES 相关的 native 库 │
│ │
│ 4. preloadSharedLibraries() │
│ ・加载 libc.so, libm.so, libdl.so │
│ ・加载 android.so, libart.so 等 │
│ │
│ 5. preloadTextResources() │
│ ・预加载字体资源 │
└───────────────────────────────────────────────────────────────┘preloadClasses() 详解
preloadClasses() 加载的是 frameworks/base/preloaded-classes 文件里的类:
# 查看预加载的类列表(部分)
adb shell cat /system/etc/preloaded-classes | head -50
# android.app.Activity
# android.app.Application
# android.app.ContextImpl
# android.app.Dialog
# android.content.ContentProvider
# android.content.Intent
# ...这个列表在 AOSP 编译时由 class_loader.jar 生成,记录了”哪些类必须在 Zygote 阶段加载”。列表里大约有 2000 个类。
为什么 preload 耗时
预加载不是单纯的读文件——JVM 加载一个类需要:
1. 读取 .dex 或 .jar 文件
2. 解析 DEX 格式(Header + ClassDefs + MethodIds)
3. 验证字节码(Bytecode Verifier)
4. 链接(Link)(解析字符串常量、设置虚方法表 vtable)
5. 初始化(<clinit> 静态块执行)某些类的 <clinit> 里有耗时操作(如静态字段初始化时读取文件)。所以 Android 9+ 引入了 deferred preloading——不在启动时预加载所有类,而是 fork 时按需加载。
Zygote Socket
Zygote 启动时创建了一个 Unix Domain Socket,路径是 /dev/socket/zygote。AMS(ActivityManagerService)通过这个 socket 发送 fork 请求。
协议格式
AMS 发给 Zygote 的请求是一个 ASCII 字符串:
# 格式:<command> <args>
# 例如:
bindle apk_fd=23 umask=0x1b nice=0 is_zygote=true primary_zygote=falseZygote 收到后会 fork 一个子进程,然后把 apk_fd(APK 文件描述符)通过 zygote Socket 发给子进程。
┌─────────────────────────────────────────────────────────────┐
│ AMS → Zygote │
├─────────────────────────────────────────────────────────────┤
│ AMS.sendRequest("bindle apk_fd=23 umask=...") │
│ │ │
│ │ Unix Domain Socket (/dev/socket/zygote) │
│ ▼ │
│ Zygote fork() → 子进程(APP) │
│ │ │
│ │ 把 apk_fd 通过 SocketTransfer 给子进程 │
│ ▼ │
│ 子进程执行 ActivityThread.main() │
└─────────────────────────────────────────────────────────────┘为什么用 Unix Domain Socket 而不是 Pipe
- 双向通信:fork 前子进程可以通过 socket 从父进程获取文件描述符
- 支持 fd 传递:普通的 pipe 只能传数据,不能传文件描述符。Zygote 需要把 APK fd 传给子进程
- 支持抽象 socket:不需要文件系统路径
fork 模型
Zygote 使用 fork() 系统调用创建 APP 进程。这是类 Unix 系统创建进程的标准方式。
Android 的 fork 实现
// frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
pid_t Zygote::forkAndSpecialize(...) {
// 1. 设置进程名(android.process.*)
// 2. 设置 NIC(网络接口能力,CAP_NET_RAW 等)
// 3. 解析 uid/gid/capabilities 参数
// 4. 调用 linux fork()
return fork();
}fork 之后发生了什么
Parent (Zygote) Child (APP 进程)
─────────────────────────────────────────────────────────────
继续运行 runSelectLoop() 继承地址空间(copy-on-write)
独立 uid/gid/capabilities
创建新的进程命名空间(如果配置了)
执行 Java 代码(Application.onCreate)为什么 fork 这么快
因为 copy-on-write(COW):
- fork 的一瞬间只是复制页表(4KB × 进程地址空间),不复制实际内存
- 父进程和子进程共享所有只读内存(类代码、虚拟函数表等)
- 只有子进程开始写数据时,才会复制那一页
这使得 APP 进程启动只需要极少的内存复制。理论上,一个 512MB 的进程地址空间,fork 只需要复制几十 KB 的页表。
SystemServer
在 ZygoteInit.main() 中,Zygote 启动后立即 preLoadAndStartSystemServer() 创建 SystemServer。
SystemServer 是什么
SystemServer 是 Android 核心系统服务的容器进程。它在 Zygote 启动后立即被 fork 出来:
init → Zygote → SystemServer(fork)
│
├── ActivityManagerService
├── PackageManagerService
├── WindowManagerService
├── PowerManagerService
├── BatteryService
├── ContentService
└── ... 100+ 系统服务SystemServer 的启动流程
// frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
// 1. 准备 Looper
Looper.prepareMainLooper();
// 2. 加载 native 库(libandroid_servers.so)
System.loadLibrary("android_servers");
// 3. 创建 SystemServiceManager
mSystemServiceManager = new SystemServiceManager(mSystemContext);
// 4. 启动引导服务(Bootstrap Services)
// - PowerManagerService
// - ActivityManagerService
// - PackageManagerService
startBootServices();
// 5. 启动核心服务(Core Services)
// - BatteryService
// - UsageStatsService
startCoreServices();
// 6. 进入消息循环
Looper.loop();
}AMS 的特殊角色
ActivityManagerService(AMS)是 SystemServer 里最重要的服务。它负责:
- 管理所有 Activity 的生命周期
- 调度 Task 和 Stack
- 启动新的 APP 进程(通过 Zygote)
- 处理广播和 Intent 解析
AMS 启动后,会执行 finishBooting()——这就是 sys.boot_completed 被设置为 1 的时机:
AMS.finishBooting()
│
▼
设置 sys.boot_completed=1
│
▼
发送 ACTION_BOOT_COMPLETED 广播
│
▼
调用 mPersistentPerApp.setProcessReady() 通知所有 APPZygote 的演进
Dalvik 时代(Android 2.2-4.4)
Zygote 设计最初就是为了解决 Dalvik 虚拟机加载慢的问题。当时没有 ART,Dalvik 的解释执行非常慢,预加载类库是唯一的优化手段。
ART 时代(Android 5.0-7.0)
ART 引入 AOT(Ahead-of-Time)编译,pre-initialized DEX( oat)文件在安装时生成。但 Zygote 的预加载逻辑基本没变。
Android 9+ 改进
Google 发现 Zygote 预加载所有类还是太慢了(2-4秒),引入了两个改进:
- Deferred preloading:不一次性加载所有类,而是记录哪些类会被用到,按需加载
- Zygote as a Service:Zygote 进程本身可以作为”服务”被多个”Zygote 实例”fork
Android 12+ 的改进
Android 12 引入了 Zygote pre-forking 和 statsd 预热。当检测到用户行为模式时(如插入 SIM 卡),提前 fork 好进程,减少用户感知的启动延迟。
APP 进程的诞生
完整的 APP 启动链路:
用户点击桌面图标
│
▼
┌───────────────────────────────────────────────────────────────┐
│ ActivityManagerService.startActivity() │
│ → 检查目标 Activity 是否已在运行 │
│ → 如果没有,创建新进程 │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ AMS.startProcessLocked() │
│ → 创建 ActivityRecord │
│ → 创建 ProcessRecord │
│ → 通过 Zygote 进程间通信请求 fork │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ ZygoteProcess.zygoteSendArgs() │
│ → 向 /dev/socket/zygote 写入请求("bindle ...") │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ ZygoteInit.main() ← runSelectLoop() │
│ → 读取请求,fork() │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 子进程(APP 进程) │
│ 1. 执行 handleChildProc() │
│ 2. 调用 ZygoteInit.invokeSystemServer() │
│ 3. 创建 RuntimeInit.ApplicationInit() │
│ 4. 执行 ActivityThread.main() │
│ 5. 加载 APK dex 文件 │
│ 6. 执行 Application.onCreate() │
│ 7. 执行 Activity.onCreate() │
└───────────────────────────────────────────────────────────────┘和 Windows 进程创建的对比
| 维度 | Android Zygote | Windows |
|---|---|---|
| 进程创建 | fork() + copy-on-write | CreateProcess() |
| 类共享 | Zygote 预加载后 fork 共享 | DLL(进程启动时加载) |
| 地址空间隔离 | fork 隔离(独立地址空间) | 独立地址空间 |
| 服务端 | Zygote Socket (UDS) | CreateProcess API |
| APP 模型 | 四大组件 | WinMain / WndProc |
核心区别:Windows 用 DLL 做代码共享,DLL 在进程启动时由 ntdll.dll → kernel32.dll → app.exe 的导入表触发加载。Android 用 Zygote 做代码共享,通过 fork 把已加载的类直接共享给子进程。
Wandos 对比
Wandos 还没有实现类似 Zygote 的机制。它的 APP 进程创建是:
wandos kernel → 自研 init → 直接 execve() APP 程序没有进程间类共享,没有 fork + copy-on-write。这对于教学目的足够了,但性能上远远比不上 Android 的 Zygote 设计。
动手环节
1. 查看 Zygote 进程
adb shell ps -A | grep zygote
# USER PID PPID VSZ RSS NAME
# u0_a97 987 1 0 0 zygote64
# u0_a97 988 1 0 0 zygote64_secondary2. 分析 Zygote 日志
adb shell logcat -b events | grep zygote
# 可以看到 fork 新进程的时机3. 测试 fork 过程
# 查看 APP 进程的 fork 时间点
adb shell logcat -b events | grep "bindle_process"
# 查看 SystemServer 启动
adb shell logcat -b events | grep system_server4. 分析 preloaded-classes
# 看预加载了哪些类
adb shell cat /system/etc/preloaded-classes | wc -l
# 大约 2000 行
# 看预加载耗时(从 dmesg 或 logcat 的 events)
adb shell dmesg | grep preinit常见问题排查
| 症状 | 原因 | 解决 |
|---|---|---|
| APP 首次启动奇慢 | preload 时间太长 | 使用 Zygote pre-forking |
| Zygote 崩溃导致所有 APP 崩溃 | fork 风暴(子进程继承父进程状态) | 检查 native crash |
| APP 启动后类找不到 | 类不在 preloaded-classes 里 | 添加到 preloaded-classes |
| Zygote Socket 连接失败 | socket 权限或 SELinux | 检查 sepolicy |
| fork() 返回 -1(ENOMEM) | 地址空间耗尽 | 检查进程数限制 |
系列导航
- 330 Android 启动总览:整体链路、时间分布、boot_completed 机制
- 331 Android Bootloader:Boot ROM → Bootloader → Kernel
- 332 Android init 进程:init.rc 配置、服务启动、属性服务
附录:关键 logcat 标签
# Zygote fork 日志
adb logcat -b events | grep "zygote"
# APP 进程创建
adb logcat -b events | grep "proc_start"
# SystemServer 启动
adb logcat -b events | grep "system_server"
# boot_completed 事件
adb logcat -b events | grep "boot_completed"作者:公众号「解码日记」,专注于操作系统、虚拟机、内核级技术深度解析。每周三更新。
评论