Skip to content

SystemServer内存

追踪 SystemServer 的堆限制、fork 前 GC、Binder 线程、启动线程池和运行时内存观测。

基于android-17.0.0_r1
AndroidSystemServerZygoteARTBinderMemoryInfo内存诊断源码阅读

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/ARTRSS 一定同比增长
native allocation 增长NativeAllocationRegistry 等登记项ART/NativeAllocationRegistry所有 malloc 都已登记
RSS 增长进程当前驻留页kernel/Procfs都是 SystemServer 私有页
PSS 增长按共享页分摊后的驻留量Debug.MemoryInfoCOW 页全属于 SystemServer
Binder 线程增加并发执行上限变化Binder driver/runtime一定减少总内存
MemoryLimiter 触发应用 cgroup 限制事件AMS + native limiterSystemServer 被该 limiter 限制

2. Fork前清理 ​

Zygote 在创建 SystemServer 之前完成 preload 和一次 PostZygoteInitGC。这一步的设计目标是让预加载结果尽量保持可共享,而不是为了让 SystemServer 获得一个更大的堆。

源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

相关函数:ZygoteInit.main、gcAndFinalize

java
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:

java
/**
 * 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

java
// 本文注:这条设置只影响 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 还显式设置主线程优先级、消息队列和启动线程池:

java
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

java
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 是启动资源的提交屏障:

java
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 初始化

java
// 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 初始化

java
// 该区域必须在 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

java
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

java
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

java
/**
 * 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

java
// 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

java
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 metricsfinalizer/close 是否注册且可达
RSS 增长但 Java heap 平稳smaps、匿名页、Binder、线程栈是共享页私有化还是 native mmap
只在启动后增长InitThreadPool pending tasksshutdown 是否成功,任务是否保留引用
只在特定设备增长feature flag/服务条件是否只在某个 HAL/overlay 分支创建对象
dumpsys 偶发缺少进程Debug.getMemoryInfo(pid) 返回 false进程是否在采集期间退出

SystemServerInitThreadPool 的 mPendingTasks、SystemServerDumper 的 dumpable map 和各服务自己的缓存是不同 owner;不能用一个全局 System.gc() 证明泄漏已修复。GC 只能回收不可达对象,不能清理仍被回调、消息队列或 static 集合持有的对象。

13. 诊断命令 ​

bash
# 输入: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、线程池和服务阶段:

bash
# 输入:预先准备好的 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 内存增长时,先从生命周期和释放点搜索:

bash
# 输入: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 上限。

bash
# 输入: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,再沿引用、生命周期、锁和失败返回寻找消费者,才有可能把“内存变大”转化为可验证的源码修改。