SystemServer内存
本文面向已经读过 SystemServer.run、预加载优化、并行服务启动 和 崩溃恢复链 的读者。本文不提供某个设备的“合理 MB 数值”,也不把应用进程的 LMK 策略当作 SystemServer 优化;它要解决的是源码问题:SystemServer 的内存边界在什么时候由谁设置,哪些页面可以通过 Zygote fork 共享,哪些对象只能在 SystemServer 自己的堆中分配,以及发生增长时如何取得可比较的证据。
system_server 的内存管理分成四个 owner:Zygote 负责 fork 前的共享堆准备,SystemServer 负责本进程 VM/Binder/线程初始化,ART/VMRuntime 负责 GC 与 native allocation 统计,ActivityManager 的 MemoryLimiter 负责被监控的应用进程。dumpsys meminfo system_server 只是观测入口,不是优化策略本身。把这几个 owner 混在一起,会得到“clearGrowthLimit 会释放内存”“增加 Binder 线程一定更快”或“MemoryLimiter 会限制 system_server”这类错误结论。
读完后,你应能沿着 ZygoteInit.main() → gcAndFinalize() → SystemServer.run() 复述一次启动期内存控制;能解释 clearGrowthLimit() 与 Zygote COW 的先后关系;能从 ActivityManagerService.dumpApplicationMemoryUsage() 判断 Debug.MemoryInfo 的输入、PSS 汇总和失败处理;能用 ProcfsMemoryUtil 与 post-GC atom 区分 Java heap、native allocation、RSS、anon RSS 和 swap;还能证明为什么 SystemServerInitThreadPool.shutdown() 是启动资源的生命周期提交点。
1. 内存边界
下图只画源码中有明确 owner 的边界,不给出没有版本依据的固定容量。
clearGrowthLimit() 只改变 ART 堆的增长限制;它不会把已经分配的对象搬走,也不会自动减少 RSS。MemoryLimiter 的 Java 注释明确说它监控的是 application processes,SystemServer 只是创建并持有这个 controller 的进程之一。内存诊断必须先确定你正在测量哪一个 owner。
| 现象 | 正确口径 | 主要 owner | 不能直接推出 |
|---|---|---|---|
| Java heap 增长 | ART managed heap 的分配/回收 | VMRuntime/ART | RSS 一定同比增长 |
| native allocation 增长 | NativeAllocationRegistry 等登记项 | ART/NativeAllocationRegistry | 所有 malloc 都已登记 |
| RSS 增长 | 进程当前驻留页 | kernel/Procfs | 都是 SystemServer 私有页 |
| PSS 增长 | 按共享页分摊后的驻留量 | Debug.MemoryInfo | COW 页全属于 SystemServer |
| Binder 线程增加 | 并发执行上限变化 | Binder driver/runtime | 一定减少总内存 |
| MemoryLimiter 触发 | 应用 cgroup 限制事件 | AMS + native limiter | SystemServer 被该 limiter 限制 |
2. Fork前清理
Zygote 在创建 SystemServer 之前完成 preload 和一次 PostZygoteInitGC。这一步的设计目标是让预加载结果尽量保持可共享,而不是为了让 SystemServer 获得一个更大的堆。
源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
相关函数:ZygoteInit.main、gcAndFinalize
if (!enableLazyPreload) {
bootTimingsTraceLog.traceBegin("ZygotePreload");
EventLog.writeEvent(
LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preload(bootTimingsTraceLog);
EventLog.writeEvent(
LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd();
}
// 本文注:fork 前清理的是 Zygote 预加载阶段产生的垃圾。
bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
gcAndFinalize();
bootTimingsTraceLog.traceEnd();
Zygote.initNativeState(isPrimaryZygote);
ZygoteHooks.stopZygoteNoThreadCreation();
zygoteServer = new ZygoteServer(isPrimaryZygote);
if (startSystemServer) {
Runnable r = forkSystemServer(
abiList, zygoteSocketName, zygoteServer);
if (r != null) {
// child: 继续执行 SystemServer;parent: 回到 Zygote loop。
r.run();
return;
}
}这里的 r == null/r != null 是 fork 后 parent/child 的分界。GC 先于 forkSystemServer(),所以预加载堆的干净程度影响 parent Zygote 与 child SystemServer 的共享页;GC 后再创建 ZygoteServer,避免把额外的 Java 状态带入 COW 共享集。
gcAndFinalize() 本身只是转交给 ZygoteHooks:
/**
* Runs several special GCs to try to clean up a few generations of softly- and final-reachable
* objects, along with any other garbage. This is only useful just before a fork().
*/
private static void gcAndFinalize() {
ZygoteHooks.gcAndFinalize();
}这个方法没有返回内存统计,也没有“达到某个堆大小才继续”的分支。若启动变慢,应该用 PostZygoteInitGC trace 与前后 PSS 对比验证,而不是凭经验删除 GC。
3. SystemServer VM
SystemServer 进入 run() 后,首先处理与 VM、Binder、SQLite 和线程调度相关的进程级设置。它们发生在大多数系统服务创建之前,后续服务继承这些默认值。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关函数:SystemServer.run
// 本文注:这条设置只影响 system_server 的 ART heap growth limit。
VMRuntime.getRuntime().clearGrowthLimit();
Build.ensureFingerprintProperty();
Environment.setUserRequired(true);
BaseBundle.setShouldDefuse(true);
Parcel.setStackTraceParceling(true);
// system_server 内部对未标注为 oneway 的阻塞 Binder 调用发出 warning。
Binder.setWarnOnBlocking(true);
SQLiteGlobal.sDefaultSyncMode =
SQLiteGlobal.SYNC_MODE_FULL;
SQLiteCompatibilityWalFlags.init(null);
// Binder 调用默认以 foreground 调度,线程上限在此设置。
BinderInternal.disableBackgroundScheduling(true);
BinderInternal.setMaxThreads(sMaxBinderThreads);clearGrowthLimit() 是允许 heap 继续增长,不是预分配 heapsize;因此它不能被解释为“立即占用更多物理内存”。BinderInternal.setMaxThreads(31) 设定的是 system_server 的 Binder 线程上限,实际线程数仍由 Binder driver 在有请求时创建。SQLiteGlobal 的 FULL sync mode 是一致性/持久化选择,不是 Java heap 压缩策略;把它改成内存优化开关会改变数据库故障语义。
SystemServer 还显式设置主线程优先级、消息队列和启动线程池:
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_FOREGROUND);
MessageQueue.setUseDeliQueue(true);
Looper.prepareMainLooper();
Looper.getMainLooper().setSlowLogThresholdMs(
SLOW_DISPATCH_THRESHOLD_MS,
SLOW_DELIVERY_THRESHOLD_MS);
// 启动期并行任务共用一个固定大小的 executor。
SystemServerInitThreadPool.start();
mDumper.addDumpable(
SystemServerInitThreadPool.getInstance());主线程优先级和 DeliQueue 影响调度与消息延迟,不会直接缩小 heap。线程池的内存成本必须和任务生命周期一起观察:它只为启动阶段服务,PHASE_BOOT_COMPLETED 后由 SystemServer 调用 shutdown。
4. 启动线程池
SystemServerInitThreadPool 是一个容易被误解的内存来源。线程数默认等于 Runtime.getRuntime().availableProcessors(),但它不是永久线程池;任务结束和 shutdown 成功后,静态实例置空,线程可回收。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServerInitThreadPool.java
相关函数:构造函数、submitTask、shutdown
private SystemServerInitThreadPool() {
mSize = Runtime.getRuntime().availableProcessors();
Slog.i(TAG,
"Creating instance with " + mSize + " threads");
mService = ConcurrentUtils.newFixedThreadPool(
mSize,
"system-server-init-thread",
Process.THREAD_PRIORITY_FOREGROUND);
}
private <T> @NonNull Future<T> submitTask(
@NonNull Callable<T> callable,
@NonNull String description) {
synchronized (mPendingTasks) {
Preconditions.checkState(
!mShutDown, TAG + " already shut down");
mPendingTasks.add(description);
}
return mService.submit(() -> {
// 本文注:pending task 名称既用于 dump,也用于 shutdown 失败诊断。
T result = callable.call();
synchronized (mPendingTasks) {
mPendingTasks.remove(description);
}
return result;
});
}mPendingTasks 由独立锁保护,避免 dump 或 shutdown 与提交任务发生数据竞争。任务抛出 RuntimeException 时,真实源码会记录失败并重新抛出;在简化阅读时不能把它当成“失败后自动释放所有任务引用”。
shutdown 是启动资源的提交屏障:
static void shutdown() {
Slog.d(TAG, "Shutdown requested");
synchronized (LOCK) {
if (sInstance == null) {
Slog.wtf(TAG, "Already shutdown", new Exception());
return;
}
synchronized (sInstance.mPendingTasks) {
sInstance.mShutDown = true;
}
sInstance.mService.shutdown();
final boolean terminated =
sInstance.mService.awaitTermination(
SHUTDOWN_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS);
if (!terminated) {
// 先 dump 线程,再 shutdownNow,保留未完成任务的现场。
dumpStackTraces();
}
final List<Runnable> unstartedRunnables =
sInstance.mService.shutdownNow();
if (!terminated) {
throw new IllegalStateException(
"Cannot shutdown. Unstarted tasks "
+ unstartedRunnables);
}
sInstance = null;
}
}正常路径是 shutdown() → 等待已提交任务完成 → sInstance=null。超时路径先收集栈,再 shutdownNow(),最后抛出异常;它不会静默地把未完成任务当成已释放。若服务启动完成后线程池仍有大量任务,应该检查提交者和 shutdown 位置,而不是只调小线程数。
5. Binder线程
Binder 线程是 SystemServer 内存与并发的耦合点。setMaxThreads() 设置上限,disableBackgroundScheduling(true) 改变进入 system_server 的 Binder 调度策略;它们不能从源码推出每线程固定占用量,因为线程栈、JNI、事务和服务调用深度都会变化。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关位置:SystemServer.run Binder 初始化
// system_server 作为服务端对持锁时的普通阻塞调用发出 warning。
Binder.setWarnOnBlocking(true);
// 进入 system_server 的 Binder 调用使用 foreground 调度策略。
BinderInternal.disableBackgroundScheduling(true);
// 允许 Binder driver 按需扩展服务端线程,但设置硬上限。
BinderInternal.setMaxThreads(sMaxBinderThreads);这些设置的 consumer 是 Binder runtime/driver 和服务调用线程,而不是 Java GC。线程数不足可能使请求排队、拉长事务持有时间;线程数过高则增加栈和调度成本。实际调优需要同时记录 Binder transaction latency、线程数和 RSS,不能只看 sMaxBinderThreads 常量。
system_server 主线程还通过 Binder.setWarnOnBlocking(true) 暴露设计违规。warning 出现时,优先定位持锁 Binder 调用和调用方向;把 warning 关闭当作内存优化会隐藏死锁和线程堆积来源。
6. 共享内存
SystemServer 在启动所有服务前创建 ApplicationSharedMemory。它的目的不是储存 SystemServer 的 Java 对象,而是建立供应用进程读取的共享区域。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关位置:SystemServer.run shared memory 初始化
// 该区域必须在 system services 启动前建立,后续服务可能依赖它。
ApplicationSharedMemory instance =
ApplicationSharedMemory.create();
ApplicationSharedMemory.setInstance(instance);
createSystemContext();
ActivityThread.initializeMainlineModules();
ServiceManager.addService(
"system_server_dumper", mDumper);共享区域的 owner 是 ApplicationSharedMemory,SystemServer 只是创建和发布实例。对 RSS/PSS 的解释要注意共享页会在多个进程之间分摊;不能把 system_server 的 PSS 直接当成该区域的物理页总量。
7. GC观测
Android 17 的 SystemServer 在满足 feature flags 时注册 post-GC callback。callback 的生效时机是 ART 完成一次 cleanup 后,而不是每次 Java 分配;它读取 native allocation registry 和 procfs snapshot,再写入 stats atom。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
相关位置:SystemServer.run post-GC callback
if (android.app.Flags.reportPostgcMemoryMetrics()
&& com.android.libcore.readonly.Flags
.postCleanupApis()) {
VMRuntime.addPostCleanupCallback(
new Runnable() {
@Override public void run() {
MetricsLoggerWrapper
.logPostGcMemorySnapshot();
}
});
}两个 flag 都为 true 才注册 callback。feature 关闭时,不能因为没有 POSTGC_MEMORY_SNAPSHOT 就断定 SystemServer 没有 GC;只是没有这条统计输出。
源码文件:frameworks/base/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
相关函数:logPostGcMemorySnapshot
public static void logPostGcMemorySnapshot() {
if (!com.android.libcore.readonly.Flags
.nativeMetrics()) {
return;
}
int pid = Process.myPid();
String processName = Process.myProcessName();
Collection<NativeAllocationRegistry.Metrics> metrics =
NativeAllocationRegistry.getMetrics();
int nMetrics = metrics.size();
String[] classNames = new String[nMetrics];
long[] mallocedCount = new long[nMetrics];
long[] mallocedBytes = new long[nMetrics];
long[] nonmallocedCount = new long[nMetrics];
long[] nonmallocedBytes = new long[nMetrics];
// ... 填充每类 registry 的 count/bytes。
ProcfsMemoryUtil.MemorySnapshot m =
ProcfsMemoryUtil
.readMemorySnapshotFromProcfs();
if (m != null) {
int oomScoreAdj =
ProcfsMemoryUtil
.readOomScoreAdjFromProcfs();
FrameworkStatsLog.write(
FrameworkStatsLog.POSTGC_MEMORY_SNAPSHOT,
m.uid, processName, pid, oomScoreAdj,
m.rssInKilobytes,
m.anonRssInKilobytes,
m.swapInKilobytes,
m.anonRssInKilobytes
+ m.swapInKilobytes,
classNames, mallocedCount,
mallocedBytes, nonmallocedCount,
nonmallocedBytes,
Runtime.getRuntime().freeMemory(),
Runtime.getRuntime().totalMemory(),
Runtime.getRuntime().maxMemory());
}
}这个 consumer 把两种口径放在同一个 atom:NativeAllocationRegistry 的登记字节数和 procfs 的 RSS/anon/swap。m == null 时不会写快照;因此缺失 atom 可能来自 procfs 读取失败或 feature 关闭,而不是内存为零。registry 也不覆盖所有 native malloc,诊断时需要和 dumpsys meminfo 的 native heap、mmap 一起看。
8. Limiter边界
MemoryLimiter 名称很容易让人误以为它限制 SystemServer 自己。实际类注释写明:它监控 application processes,Java 层把进程信息发给 native 层,native 层在 cgroup limit 触发时回调 Java。
源码文件:frameworks/base/services/core/java/com/android/server/am/MemoryLimiter.java
相关类:MemoryLimiter
/**
* This class monitors the amount of memory used by application processes.
* Debug data is collected if a process exceeds its limits, and the process
* may be killed. The limits (and action) vary by process category.
* The java class sends process information into the native layer where the
* limits are applied.
*
* This class is not thread-safe. Production code should call APIs while
* holding the AMS lock.
*/
class MemoryLimiter implements AutoCloseable {
static final int LIMIT_TYPE_UNKNOWN = 0;
static final int LIMIT_TYPE_MEMORY = 1;
static final int LIMIT_TYPE_SWAP = 2;
static final int LIMIT_TYPE_ANON_SWAP = 3;
static final long KILL_DELAY_MS = 30 * 1000;它的 owner 是 AMS 中的 limiter/controller,状态需要 AMS lock 保护,监控对象是应用进程的 UID/PID。SystemServer 的 Java heap 增长、Binder 线程或服务缓存不经过这条应用 cgroup limit 路径;用 MemoryLimiter 的配置去解释 system_server RSS 是对象层级错误。
AMS 创建并在 system ready 时初始化 limiter:
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
相关位置:构造函数、systemReady
// ActivityManagerService 构造期创建 controller。
mMemoryLimiter =
MemoryLimiter.getDefaultMemoryLimiter(mContext);
// ActivityManager 完成控制器初始化后,通知 limiter 开始工作。
mMemoryLimiter.onSystemReady();onSystemReady() 是 limiter 的生命周期边界,不是 SystemServer VM 初始化边界。若某设备没有默认配置或内存不足,isMemoryLimiterSupported() 可以返回 false;这只意味着应用进程限制功能未启用。
9. 内存观测
dumpsys meminfo system_server 最终进入 ActivityManager 的内存汇总逻辑。它按进程收集 Debug.MemoryInfo,再将 Java、native、dalvik、graphics 和其他类别聚合为表格。输出中的 PSS 是分摊值,不能简单相加得到物理页数量。
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
相关函数:dumpApplicationMemoryUsage
final int numProcs = procs.size();
final boolean collectNative =
!opts.isCheckinRequest
&& numProcs > 1
&& !opts.packages;
if (collectNative) {
// 聚合多个 Java 进程时,额外寻找 native processes。
updateCpuStatsNow();
}
dumpApplicationMemoryUsageHeader(
pw, uptime, realtime,
opts.isCheckinRequest, opts.isCompact);
Debug.MemoryInfo mi = null;
for (int i = numProcs - 1; i >= 0; i--) {
final ProcessRecord r = procs.get(i);
final int pid;
synchronized (mProcLock) {
pid = r.getPid();
}
if (mi == null) {
mi = new Debug.MemoryInfo();
}
if (!Debug.getMemoryInfo(pid, mi)) {
// 进程在收集期间退出时,跳过这一项而不是写入伪数据。
continue;
}
ActivityThread.dumpMemInfoTable(
pw, mi, opts.isCheckinRequest,
opts.dumpFullDetails, opts.dumpDalvik,
opts.dumpSummaryOnly, pid, r.processName,
0, 0, 0, 0, 0, 0);
}mProcLock 只保护从 ProcessRecord 读取 PID 的瞬间;Debug.getMemoryInfo() 在锁外执行,避免把慢的 procfs 读取带入 AMS 锁。进程退出返回 false 是正常竞态,不能把它当成低内存或 kernel 读取错误。
要理解输出中的 Java/native 分界,还要看 Debug.MemoryInfo 的 dumpMemInfoTable() 展示规则;同一页可能被多个映射或进程共享,表格是诊断视图,不是 allocator 的完整账本。
10. PSS与RSS
内核提供的 /proc/<pid>/smaps_rollup、/proc/<pid>/status 和 Debug.getMemoryInfo() 是不同层级的读数。SystemServer 优化前必须确定问题是 heap retention、native allocation、共享 COW 页还是文件映射驻留。
dumpsys meminfo 更适合按类别展开单次现场,post-GC atom 更适合跨版本/跨设备聚合趋势;两者不应期待数值逐项相等。PSS 会把共享页按映射关系分摊,RSS 则反映当前进程实际驻留页;COW 共享的预加载页发生写入后,PSS 和 anon RSS 的变化也可能不同。
11. 启动时序
这条时序说明了三个生效点:fork 前 GC 影响共享页,SystemServer clearGrowthLimit 影响后续分配上限,post-GC callback 只在 cleanup 完成后观测。启动线程池 shutdown 与 metrics callback 没有直接因果关系;一个释放启动 executor,另一个注册运行期统计。
12. 泄漏定位
SystemServer 的长期增长通常来自服务对象、缓存、回调、Handler 消息或 native registration;源码层面的定位顺序应从“谁持有引用”开始,而不是先触发 GC。
| 现象 | 首先查 | 关键问题 |
|---|---|---|
| Java heap after GC 持续增长 | 服务字段、集合、回调列表 | owner 是否随 user/package/UID 删除 |
| native registry bytes 增长 | NativeAllocationRegistry metrics | finalizer/close 是否注册且可达 |
| RSS 增长但 Java heap 平稳 | smaps、匿名页、Binder、线程栈 | 是共享页私有化还是 native mmap |
| 只在启动后增长 | InitThreadPool pending tasks | shutdown 是否成功,任务是否保留引用 |
| 只在特定设备增长 | feature flag/服务条件 | 是否只在某个 HAL/overlay 分支创建对象 |
| dumpsys 偶发缺少进程 | Debug.getMemoryInfo(pid) 返回 false | 进程是否在采集期间退出 |
SystemServerInitThreadPool 的 mPendingTasks、SystemServerDumper 的 dumpable map 和各服务自己的缓存是不同 owner;不能用一个全局 System.gc() 证明泄漏已修复。GC 只能回收不可达对象,不能清理仍被回调、消息队列或 static 集合持有的对象。
13. 诊断命令
# 输入:system_server PID。输出:Java/native/dalvik/graphics/PSS 分类汇总。
adb shell dumpsys meminfo system_server
# 输入:system_server PID。输出:smaps_rollup 的 RSS/PSS/匿名页/文件页汇总。
adb shell cat /proc/$(pidof system_server)/smaps_rollup
# 输入:system_server PID。输出:OOM score、线程数和进程状态。
adb shell cat /proc/$(pidof system_server)/status \
| rg '^(Name|Pid|PPid|Uid|Threads|VmRSS|VmSize|RssAnon|RssFile|RssShmem|VmSwap):'
# 输入:SystemServerDumper。输出:启动线程池 pending task 和服务 dumpable 状态。
adb shell dumpsys system_server_dumper --name SystemServerInitThreadPool
# 输入:Binder 调试服务。输出:设备支持的 Binder 调用/事务统计;服务不存在时先看 dumpsys -l。
adb shell dumpsys binder_calls_stats
adb shell dumpsys binder_transactions命令的判断顺序应固定:先用 meminfo 看分类,再用 procfs 看 RSS/PSS 口径,最后用 SystemServerDumper 和 Binder 统计定位 owner。Binder 调试服务在不同构建中可能被裁剪,命令失败不等于 SystemServer 内存异常。
启动期还可以用 Perfetto trace 关联 GC、线程池和服务阶段:
# 输入:预先准备好的 Perfetto 文本配置和设备。输出:20 秒 system_server trace 文件。
adb shell perfetto --txt -o /data/misc/perfetto-traces/system_server_memory.pftrace \
-t 20s -c /data/local/tmp/system_server_memory.pbtxt
adb pull /data/misc/perfetto-traces/system_server_memory.pftrace .该命令要求设备存在对应的 Perfetto 配置和 data source;如果只启用了 sched trace,就只能关联线程调度,不能推导 Java heap 大小。trace 需要与 logcat 中的 PostZygoteInitGC、InitThreadPoolExec:* 和服务 trace 一起解释。
14. 源码搜索
当怀疑某个服务导致 SystemServer 内存增长时,先从生命周期和释放点搜索:
# 输入:AOSP frameworks/base 源码。输出:SystemServer 进程级内存设置和 GC 观测入口。
rg -n 'clearGrowthLimit|setMaxThreads|disableBackgroundScheduling|addPostCleanupCallback|ApplicationSharedMemory' \
frameworks/base/services/java/com/android/server/SystemServer.java \
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
# 输入:AOSP frameworks/base 源码。输出:meminfo 汇总、Debug.MemoryInfo 和失败跳过路径。
rg -n 'dumpApplicationMemoryUsage|Debug\.getMemoryInfo|dumpMemInfoTable|MemoryInfo' \
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
# 输入:AOSP frameworks/base 源码。输出:启动线程池的提交、pending 和 shutdown 消费者。
rg -n 'SystemServerInitThreadPool|mPendingTasks|awaitTermination|shutdownNow' \
frameworks/base/services/core/java/com/android/server/SystemServerInitThreadPool.java \
frameworks/base/services/java/com/android/server/SystemServer.java
# 输入:AOSP frameworks/base 源码。输出:MemoryLimiter 的应用进程边界和 native 回调。
rg -n 'MemoryLimiter|onLimitExceeded|configureLimit|application processes' \
frameworks/base/services/core/java/com/android/server/am \
frameworks/base/services/core/jni/com_android_server_am_MemoryLimiter.cpp这些命令的输入是固定 release 的源码树,输出是继续阅读的符号位置。最后一条搜索尤其用于排除误判:如果修改的是 SystemServer Java heap,却只看到 MemoryLimiter 的应用 cgroup 回调,说明搜索对象已经偏离问题边界。
15. 测试边界
内存行为很难由单元测试完全证明,因为真实 RSS/PSS、内核页面回收、Binder driver 和设备服务集合都在测试环境之外。现有源码测试更适合验证局部状态和统计格式。
SystemServerInitThreadPool 的公开行为可以通过 dumpsys 观察 has instance、线程数和 pending task;真正的 shutdown 超时路径会调用 dumpStackTraces() 并抛异常,因此设备实验要区分“线程池已释放”和“shutdown 失败但进程继续运行”。
MemoryLimiter 测试应围绕 services/tests/MemoryLimiterTests 中的 MemoryLimiterTest 搜索 limit type、配置解析、cgroup 事件和 callback;这些测试证明的是应用进程限制,不证明 SystemServer 自己的 heap 上限。
# 输入:AOSP 测试源码。输出:SystemServerInitThreadPool、MemoryLimiter、内存统计相关测试。
rg -n 'SystemServerInitThreadPool|MemoryLimiter|POSTGC_MEMORY_SNAPSHOT|MemoryInfo' \
frameworks/base/services/tests \
frameworks/base/core/tests
# 输入:设备。输出:执行内存相关 framework 测试的结果摘要。
atest MemoryLimiterTests \
FrameworksServicesTests:WatchdogTest测试命令是否能运行取决于构建目标和设备测试包;即使测试通过,也只覆盖源码指定的 arrange/action/assert。要证明一次泄漏修复,仍需在同一 workload 下比较 GC 后 native/Java/PSS、Binder 线程、启动线程池 shutdown 状态和长期趋势。
16. 收束
SystemServer 内存优化的源码主线可以压缩为:Zygote preload 后执行 gcAndFinalize(),在 fork 前尽量保持共享页干净;SystemServer run() 进入后用 clearGrowthLimit() 放开 managed heap 的增长边界,设置 Binder 调度/线程上限、SQLite 默认模式、主线程和启动线程池;服务启动期间用 Debug.MemoryInfo、PSS/RSS 和 trace 观察;启动完成后关闭 SystemServerInitThreadPool;若 feature flags 开启,ART cleanup 后再由 MetricsLoggerWrapper 合并 native registry 与 procfs 快照。
这条链还说明了三个不能混淆的结论:解除增长限制不等于立即占用内存,增加 Binder 上限不等于无代价扩容,MemoryLimiter 不等于 SystemServer 自身的内存守护。定位问题时先写清楚测量口径和 owner,再沿引用、生命周期、锁和失败返回寻找消费者,才有可能把“内存变大”转化为可验证的源码修改。
