1. Zygote 进程与预加载机制概述
Zygote 是 Android 系统的“孵化器”进程,是所有 Java 应用进程的父进程。系统启动时,Zygote 预先加载大量 Java 类和系统资源,然后通过 fork 快速创建新的应用进程,避免每个应用重复执行类加载和资源初始化操作。这一机制大幅缩短了应用启动时间,但也带来了预加载类清单的选择和加载顺序优化问题——这正是本章探讨的核心。
关键指标:Android 8.0 上 Zygote 预加载约 3000+ 个类,占用 ~6MB 内存,类加载耗时约占 Zygote 启动总耗时的 40%~50%。
1.1 Zygote 启动流程
Zygote 的启动由 init 进程触发,经过 Dalvik/ART 虚拟机初始化、预加载类和资源、进入 socket 监听等阶段,最终进入等待 fork 请求的循环。下表列出完整的启动步骤:
| 步骤 | 阶段 | 关键操作 | 耗时占比(近似) |
|---|---|---|---|
| 1 | init → Zygote | init 进程根据 rc 文件启动 app_process,传入 "Zygote" 参数 | 2% |
| 2 | 虚拟机初始化 | ART/Dalvik 创建 JavaVM、加载核心库(libcore) | 10% |
| 3 | 预加载类 | 依次加载 preloaded-classes 文件中声明的所有类 |
35% |
| 4 | 预加载资源 | 加载系统主题、字体、常用 drawable 等资源 | 15% |
| 5 | 注册 socket | 创建 "/dev/socket/zygote" 域套接字,开始监听 fork 请求 | 3% |
| 6 | 循环等待 | 调用 ZygoteServer.runSelectLoop() 阻塞等待 |
35% |
优化切入点:步骤 3 是 Zygote 启动中耗时最多的单一步骤,重排该阶段的类加载顺序可直接减少 Zygote 启动耗时,同时降低后续应用进程的缺页次数。
1.2 预加载类清单
预加载类的声明文件位于 /system/etc/preloaded-classes(Android 8.0 后为 /system/etc/classloader/preloaded-classes),每行一个类全限定名。Zygote 启动时按文件顺序逐类加载,先声明的类先加载。清单内容大致分为以下类别:
| 类别 | 示例类 | 说明 |
|---|---|---|
| 系统基础 | java.lang.Object, java.lang.String |
JCL 核心类,几乎所有应用都需要 |
| Framework 核心 | android.content.Context, android.app.ActivityThread |
支撑应用上下文和主线程调度 |
| UI 组件 | android.view.View, android.widget.TextView |
渲染和交互基类,高频使用 |
| Binder 框架 | android.os.Binder, android.os.IBinder |
进程间通信基础 |
| 其他常用库 | android.util.Log, android.graphics.Bitmap |
日志、图形等通用支持 |
1.3 类加载时机与内存共享原理
所有预加载类在 Zygote 进程中完成加载后,其类元数据、方法表、常量池等数据结构会驻留在 Zygote 堆中。当 Zygote fork 出一个新应用进程时,子进程通过写时拷贝(COW)机制继承父进程的堆内存:
- 预加载类的 代码段(dex 文件 mmap 映射)直接共享物理内存页,不产生拷贝。
- 堆中的 对象实例(如字符串常量、类对象)在 fork 后标记为“只读”,直到子进程试图修改时才触发 page fault 并复制。
- 类加载顺序直接影响 fork 后缺页中断的频率:若应用启动时频繁访问的类在预加载清单中排列靠后,可能因 COW 触发多次缺页,拖慢应用启动。
1.4 重排加速的核心思想
重排加速并非改变“加载哪些类”,而是优化“按什么顺序加载”。核心思想包括:
- 启动路径聚类:收集大量应用启动的类引用序列,按“首次使用时间”排序,将最先使用的类提前。
- 依赖链对齐:将一个类的所有依赖类紧挨着加载,减少跨页访问。
- 缺页惩罚最小化:将类按所属 dex 文件分组,确保相关类落在同一内存页中,降低缺页中断次数。
- 冷启动 vs 热启动平衡:既保证系统服务和系统 UI 的快速启动,又兼顾第三方应用的早期类命中率。
// 原始顺序(示例)
java.lang.Object
java.lang.String
java.lang.Class
android.app.ActivityThread
android.content.Context
android.view.View
// 重排后(按应用启动实际引用顺序聚类)
android.app.ActivityThread
android.content.Context
android.view.View
java.lang.Object
java.lang.String
java.lang.Class
注意:重排不是简单的“把罕见类放到末尾”。实验证明,错误的重排(如将常用类放到文件末尾)会在 fork 后额外触发 30%~50% 的缺页中断,导致应用冷启动速度不升反降。