Skip to content

PMS 内存结构

从 PMS 容器和快照源码分析内存随包、用户、组件和缓存增长的结构与验证方法。

基于android-17.0.0_r1
AndroidPMSMemorySnapshot

PMS 内存结构 ​

本文不使用“每个 PackageSetting 固定占多少 KB”这类无法由源码直接证明的估算,而是建立可验证的内存模型:哪些对象按包增长,哪些按用户状态增长,哪些在 snapshot 重建时复制,哪些只是共享引用。读者可先阅读 PMS 性能分析 和 per-user 包状态。

1. 内存增长维度 ​

维度主要容器增长单位
包数量PMS packages、Settings packages每个全局包
用户数量PackageSetting.mUserStates每个包的显式 user state
组件数量ComponentResolver、解析后 package每个 Activity/Service/Provider/Receiver
共享库/映射shared libraries、AppsFilter、APEX 映射每个依赖或关系
snapshotComputer 深拷贝、SnapshotCache每次有效版本重建
临时任务安装 Future、staged sessions、结果对象每个进行中的操作

“包数 × 用户数”只是 user state 的上界,不是实际对象数,因为 readUserState 对缺失条目返回默认对象,不会创建 state。

2. PMS 顶层容器 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:Snapshot 字段

java
private ComputerLocked mLiveComputer;
private static final AtomicReference<Computer>
        sSnapshot = new AtomicReference<>();
private static final AtomicInteger
        sSnapshotPendingVersion = new AtomicInteger(1);
private final Object mSnapshotLock = new Object();
private final SnapshotStatistics mSnapshotStatistics;

PMS 同时保留 live computer、当前 snapped computer、待生效版本和 snapshot lock。内存分析必须区分“源状态容器”和“当前快照容器”;快照存在时,部分结构会在源对象和副本中各有一份。

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:Snapshot

java
public final Settings settings;
public final WatchedArrayMap<String, AndroidPackage> packages;
public final AppsFilterSnapshot appsFilter;
public final ComponentResolverApi componentResolver;
public final SharedLibrariesRead sharedLibraries;
public final WatchedSparseIntArray isolatedOwners;
public final WatchedArrayMap<String, Integer> frozenPackages;

快照包含的不只是 package map,还包括 Settings、AppsFilter、resolver、shared libraries、isolated owners 和 frozen package 状态。一次 package 安装或权限/可见性变更可能导致整个读取视图重新生成,不能只计算 AndroidPackage 对象大小。

3. Per-user 乘数 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java,符号:字段声明

java
private final SparseArray<PackageUserStateImpl>
        mUserStates = new SparseArray<>();
private final SnapshotCache<PackageSetting>
        mSnapshot;
private final PackageStateUnserialized pkgState;
private final InstallSource installSource;
private final PackageSignatures signatures;

全局包字段与 user state 分离:签名、安装来源、代码路径、版本和 ABI 不随用户复制;installed、enabled、stopped、inode、archive、suspend 等状态在 mUserStates 中按 userId 存储。设备增加工作资料或访客时,主要增加的是显式 user-state 条目和相关数据结构,而不是整份 APK 解析对象。

4. 稀疏 user state ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java,符号:readUserState、modifyUserState

java
public PackageUserStateInternal readUserState(int userId) {
    PackageUserStateInternal state =
            mUserStates.get(userId);
    return state != null
            ? state : PackageUserStateInternal.DEFAULT;
}

PackageUserStateImpl modifyUserState(int userId) {
    PackageUserStateImpl state =
            mUserStates.get(userId);
    if (state == null) {
        state = new PackageUserStateImpl(this);
        mUserStates.put(userId, state);
        onChanged();
    }
    return state;
}

这是 PMS 内存结构的关键节省点:只读查询不会为每个“包 × 所有用户”预分配对象;只有真正修改某用户状态时才创建。内存诊断不能用用户总数乘包数直接代替 mUserStates.size()。

5. 布尔位与集合 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java,符号:Booleans 与字段

java
private static final int INSTALLED = 1;
private static final int STOPPED = 1 << 1;
private static final int NOT_LAUNCHED = 1 << 2;
private static final int HIDDEN = 1 << 3;
private static final int INSTANT_APP = 1 << 4;
private static final int VIRTUAL_PRELOADED = 1 << 5;
private static final int APP_LOCK_ENABLED = 1 << 6;

private int mBooleans;
private WatchedArraySet<String> mDisabledComponentsWatched;
private WatchedArraySet<String> mEnabledComponentsWatched;
private WatchedArrayMap<UserPackage, SuspendParams>
        mSuspendParams;

布尔状态被压缩到一个 int,但组件集合、挂起参数、overlay paths、archive state 和 label/icon 覆盖仍按实际内容分配。一个没有组件覆盖、没有 suspend 的普通 user state,与拥有大量组件集合的状态,内存形状不同。

6. SnapshotCache ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java,符号:makeCache 与 copy constructor

java
private SnapshotCache<PackageUserStateImpl> makeCache() {
    return new SnapshotCache<PackageUserStateImpl>(
            this, this) {
        @Override
        public PackageUserStateImpl createSnapshot() {
            return new PackageUserStateImpl(
                    mWatchable, mSource);
        }
    };
}

public PackageUserStateImpl(
        @NonNull Watchable watchable,
        PackageUserStateImpl other) {
    mBooleans = other.mBooleans;
    mDisabledComponentsWatched =
            other.mDisabledComponentsWatched == null
                    ? null
                    : other.mDisabledComponentsWatched.snapshot();
    mSuspendParams = other.mSuspendParams == null
            ? null : other.mSuspendParams.snapshot();
    mArchiveState = other.mArchiveState;
    mSnapshot = new SnapshotCache.Sealed<>();
}

snapshot 对 watched 集合做独立快照,对不可变值/对象复用引用;因此“快照副本约等于原对象两倍”不是可靠结论。复制量取决于集合内容和源对象类型。

7. 快照重建成本 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:rebuildSnapshot

java
@GuardedBy("mLock")
private Computer rebuildSnapshot(
        @Nullable Computer oldSnapshot,
        int newVersion) {
    long now = SystemClock.elapsedRealtimeNanos();
    int hits = oldSnapshot == null
            ? -1 : oldSnapshot.getUsed();
    Snapshot args = new Snapshot(Snapshot.SNAPPED);
    Computer newSnapshot = new ComputerEngine(
            args, newVersion);
    long done = SystemClock.elapsedRealtimeNanos();
    if (mSnapshotStatistics != null) {
        mSnapshotStatistics.rebuild(
                now, done, hits,
                newSnapshot.getPackageStates().size());
    }
    return newSnapshot;
}

快照统计记录重建开始/结束、旧快照使用次数和包状态数量。它能回答“重建耗时是否随 package states 增长”,但不能直接给出 Java heap 的精确字节数;字节级结论仍需 heap dump 或运行时工具验证。

8. 安装临时对象 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageMetrics.java,符号:安装步骤

java
private final SparseArray<InstallStep>
        mInstallSteps = new SparseArray<>();
private final InstallRequest mInstallRequest;
private final long mInstallStartTimestampMillis;

安装期间还会存在 InstallRequest、ReconciledPackage、PackageMetrics、Future、freeze 任务和 dexopt result。它们是临时峰值,不应与长期驻留的 PackageSetting/snapshot 混在一起;长时间安装或大量并发 session 时,应分别观察 live set 和 transient allocation。

9. 可观测性 ​

PMS 源码提供的是结构线索,不是完整 heap profiler。实际验证应结合:

  1. dumpsys package 的包/user state、APEX 和 session 数量。
  2. PackageMetrics 安装步骤与 snapshot statistics,定位临时对象生命周期。
  3. system_server heap dump,按 PackageSetting、PackageUserStateImpl、ComputerEngine、resolver/AppsFilter 容器聚类。
  4. 增加/删除用户后比较 mUserStates.size()、snapshot rebuild 和 heap retained size。
  5. 修改大量组件覆盖或 suspend 参数后,比较 watched 集合增长,而不是只看包数量。

10. 失败定位 ​

现象首先检查
用户数增加导致常驻内存增长每个 PackageSetting 的 mUserStates.size()
查询后内存短时升高snapshot rebuild、PackageInfo 组装和 Parcel 对象
状态修改后旧对象仍被引用SnapshotCache invalidation、Computer version
大包明显占用更多组件数量、metadata、shared libraries 和 watched 集合
安装结束后内存不回落InstallRequest/Future/session 引用链和 observer
APEX 数量变化影响 snapshotAPEX scan、system service list、module mappings

11. 阅读检查 ​

复述内存主线:全局 package/settings → per-user SparseArray → watched state/snapshot → Computer 深拷贝 → 查询消费者;安装另有 Future、metrics 和结果对象形成临时峰值。然后回答:为什么不能用“包数 × 用户数”直接得到 user state 数量?为什么 snapshot 不一定复制所有字符串/对象?为什么 heap dump 必须区分长期对象和安装临时对象?