preloadClasses
SS006 已给出预加载阶段地图;本篇只拆 preloadClasses()。它的输入是 /system/etc/preloaded-classes,消费者是 boot classpath 的类加载器和 ART dex cache,生效时机是 Zygote 第一次 fork 之前。重点不在“加载了很多类”,而在于源码如何处理静态初始化副作用、root 权限、缺失项、native link 错误、文件读取错误和 profile 计数。
1. 输入与owner
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
private static final String PRELOADED_CLASSES =
"/system/etc/preloaded-classes";
private static final int UNPRIVILEGED_UID = 9999;
private static final int ROOT_UID = 0;
private static final int ROOT_GID = 0;类表文件由系统镜像提供,不是 Zygote 在运行时生成;VMRuntime.getRuntime() 是 ART 侧 owner;ZygoteInit 只负责读取文件、调用 Class.forName 并在 finally 阶段通知 ART 填充 dex cache。
2. 打开与root降权
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
final VMRuntime runtime = VMRuntime.getRuntime();
InputStream is;
try {
is = new FileInputStream(PRELOADED_CLASSES);
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
return;
}
final int reuid = Os.getuid();
final int regid = Os.getgid();
boolean droppedPriviliges = false;
if (reuid == ROOT_UID && regid == ROOT_GID) {
Os.setregid(ROOT_GID, UNPRIVILEGED_GID);
Os.setreuid(ROOT_UID, UNPRIVILEGED_UID);
droppedPriviliges = true;
}文件不存在时函数记录错误并直接返回;这条路径不会进入逐类循环。只有当前 uid/gid 都是 root 才降权,WrapperInit 等非 root 调用者不会被强制切换。降权的目的,是让类静态初始化在接近普通运行权限的上下文中执行,避免预加载期间意外以 root 身份触发受保护操作。
如果 setreuid/setregid 失败会抛出 RuntimeException,跳过类加载;成功降权后,finally 必须恢复 root,因为 Zygote 后续还要执行 fork 和特权初始化。
3. 逐行加载
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
BufferedReader br = new BufferedReader(
new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
int count = 0;
int missingLambdaCount = 0;
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || line.equals("")) continue;
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
try {
Class.forName(line, true, null);
count++;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
}
}trim() 后的空行和 # 注释不参与计数;Class.forName(name, true, null) 的第二个参数 true 强制执行静态初始化,第三个参数 null 使用 boot classpath loader。这里的 count 只统计成功完成加载和初始化的条目,不等于文件行数,也不等于所有类都能被每个应用直接访问。
每个类都包围独立 trace section,启动分析可以定位单个类的初始化耗时。Trace.traceEnd() 放在 finally,确保异常也不会遗留未闭合的 trace section。
4. 异常分级
} catch (ClassNotFoundException e) {
if (line.contains("$$Lambda$")) {
if (LOGGING_DEBUG) missingLambdaCount++;
} else {
Log.w(TAG, "Class not found for preloading: " + line);
}
} catch (UnsatisfiedLinkError e) {
Log.w(TAG, "Problem preloading " + line + ": " + e);
} catch (Throwable t) {
Log.e(TAG, "Error preloading " + line + ".", t);
if (t instanceof Error) throw (Error) t;
if (t instanceof RuntimeException) throw (RuntimeException) t;
throw new RuntimeException(t);
}异常处理体现了不同失败成本:
- 普通
ClassNotFoundException:记录 warning,继续读下一行; $$Lambda$缺失:在 debug 日志开启时计数,减少预期生成类噪声;UnsatisfiedLinkError:记录 native 依赖问题,但继续预加载;- 其他
Error/RuntimeException:重新抛出,静态初始化的严重错误会终止 Zygote 启动; - 文件读取
IOException:在外层记录错误,随后仍执行 finally 收尾。
因此“日志中有 preload warning”不能直接推断 Zygote 退出;要看异常类型和是否重新抛出。
5. Dex缓存收尾
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
finally {
IoUtils.closeQuietly(is);
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadDexCaches");
runtime.preloadDexCaches();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
if (shouldProfileBootClasspath()) {
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ResetJitCounters");
VMRuntime.resetJitCounters();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
}
if (droppedPriviliges) {
Os.setreuid(ROOT_UID, ROOT_UID);
Os.setregid(ROOT_GID, ROOT_GID);
}
}IoUtils.closeQuietly 关闭输入流;preloadDexCaches() 把已加载类、字段和方法写入 ART 的 dex cache;profile boot classpath 时重置 JIT counters,让后续统计从“预加载阶段之后”开始。无论文件缺失、读取异常还是逐类异常,finally 都承担这些资源和状态收尾。
6. 主线状态图
7. Profile开关
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
private static boolean shouldProfileBootClasspath() {
return isExperimentEnabled("profilebootclasspath");
}isExperimentEnabled 读取 dalvik.vm.profilebootclasspath 和 runtime-native-boot namespace 的持久化 DeviceConfig;Zygote 阶段不能依赖尚未初始化的普通 DeviceConfig 服务。该开关只影响 JIT counter 重置和后续 profile 逻辑,不改变类表输入,也不使缺失类变成成功。
8. 导航与后续
# 输入类表路径和实现文件,输出类加载、异常与收尾位置。
rg -n "PRELOADED_CLASSES|preloadClasses|Class\.forName|preloadDexCaches|resetJitCounters" \
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
# 输入权限切换符号,输出降权与恢复路径。
rg -n "UNPRIVILEGED_UID|setreuid|setregid|droppedPrivil" \
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java下一篇将分析 preloadResources(),只处理 drawable、color state list、TypedArray recycle 和资源缓存失败;不会重复本文的类表、root 降权和 dex cache。
