Android Zygote Fork 笔记

5 阅读4分钟

Zygote 是 Android 系统的“万婴之母”,它之所以采用 fork 来创建新的应用进程,而非直接启动一个新的虚拟机实例,背后有深刻的性能和内存考量。我们可以从 Zygote 的角色、fork 的工作机制以及 Android 的实际需求三个方面来理解这一设计。


一、Zygote 的核心任务

Zygote 是 Android 中第一个 Dalvik/ART 虚拟机进程,它在系统启动时由 init 进程创建。其主要工作有三:

  1. 初始化虚拟机:创建 Dalvik/ART 实例,为运行 Java 代码准备好环境。
  2. 预加载核心资源:加载所有应用都会用到的系统类(如 android.*)、主题资源、通用数据结构等。这一步相当耗时,通常在开机时完成。
  3. 等待孵化请求:通过 Socket 监听来自 ActivityManagerService 的命令,随时准备创建新的应用进程。

二、为什么不直接创建新进程?

如果我们不采用 fork,每次启动应用时都需要:

  • 新建一个虚拟机实例;
  • 重新加载所有系统类和资源;
  • 初始化运行时环境。

这种做法有两大弊端:

  • 速度慢:加载数百个系统类、解析资源、初始化虚拟机本身就很耗时,直接导致应用启动变慢。
  • 内存浪费:每个应用独立持有相同的系统类代码和只读数据,无法共享物理内存,会消耗大量 RAM。

三、fork 的魔法:写时拷贝与资源共享

fork 是 Linux 创建子进程的标准系统调用,它的特点是:

  • 子进程是父进程的完整副本,包括代码段、数据段、堆、栈等。
  • 写时拷贝(Copy-On-Write, COW):内核不会立即复制父进程的整个地址空间,而是将父子进程的页表指向同一物理内存页,并标记为只读。当其中一方试图修改数据时,才会复制该页并分配新物理内存。

这意味着:

  • Zygote 预加载好的所有系统类、资源和虚拟机内部数据结构,都被子进程(应用进程)共享。这些只读数据在物理内存中只有一份,大大节省了内存。
  • 创建子进程的速度极快,因为不需要从零加载任何东西,只需复制父进程的页表即可。

四、fork 之后:应用专属初始化

当 Zygote 收到 AMS 的请求后,它会 fork 自身。新创建的子进程此时拥有和 Zygote 完全一样的运行时状态。随后,子进程会执行特定的应用初始化流程:

  1. 设置进程名:将进程名改为应用包名。
  2. 创建 ActivityThread 并调用其 main() 方法ActivityThread 是应用的主线程入口,它会启动 Looper,并通知 AMS 已准备就绪。
  3. 加载应用的代码和资源:通过 PathClassLoader 加载 APK 中的类,但此时系统核心类已经通过 Zygote 共享,无需再次加载。

五、一个形象的类比

可以把 Zygote 想象成一个已经布置好的厨房:里面备好了所有常用食材(系统类)、锅碗瓢盆(虚拟机)和基本调料(资源)。当有新顾客(应用)来吃饭时,我们不需要重建一个厨房,只需要直接复制这个厨房(fork),然后根据顾客的特定要求(应用代码)稍作调整即可。这样既省去了准备食材的时间,又节约了空间。

六、为什么不是全部共享?

虽然 fork 能共享只读数据,但应用中写入的数据(如静态变量、对象实例)最终会通过写时拷贝分离。这确保了各个应用进程之间的数据隔离,符合 Android 的安全模型。


总结

Zygote 使用 fork 的核心原因在于:

  • 性能:利用写时拷贝,避免了重复加载系统类和资源,应用启动速度提升数倍。
  • 内存:只读的系统资源在所有应用间物理共享,显著降低内存占用。
  • 简洁性:Zygote 作为模板,统一了所有应用进程的初始状态,简化了系统设计。