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 映射 | 每个依赖或关系 |
| snapshot | Computer 深拷贝、SnapshotCache | 每次有效版本重建 |
| 临时任务 | 安装 Future、staged sessions、结果对象 | 每个进行中的操作 |
“包数 × 用户数”只是 user state 的上界,不是实际对象数,因为 readUserState 对缺失条目返回默认对象,不会创建 state。
2. PMS 顶层容器
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:Snapshot 字段
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
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,符号:字段声明
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
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 与字段
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
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
@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,符号:安装步骤
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。实际验证应结合:
dumpsys package的包/user state、APEX 和 session 数量。PackageMetrics安装步骤与 snapshot statistics,定位临时对象生命周期。- system_server heap dump,按
PackageSetting、PackageUserStateImpl、ComputerEngine、resolver/AppsFilter 容器聚类。 - 增加/删除用户后比较
mUserStates.size()、snapshot rebuild 和 heap retained size。 - 修改大量组件覆盖或 suspend 参数后,比较 watched 集合增长,而不是只看包数量。
10. 失败定位
| 现象 | 首先检查 |
|---|---|
| 用户数增加导致常驻内存增长 | 每个 PackageSetting 的 mUserStates.size() |
| 查询后内存短时升高 | snapshot rebuild、PackageInfo 组装和 Parcel 对象 |
| 状态修改后旧对象仍被引用 | SnapshotCache invalidation、Computer version |
| 大包明显占用更多 | 组件数量、metadata、shared libraries 和 watched 集合 |
| 安装结束后内存不回落 | InstallRequest/Future/session 引用链和 observer |
| APEX 数量变化影响 snapshot | APEX scan、system service list、module mappings |
11. 阅读检查
复述内存主线:全局 package/settings → per-user SparseArray → watched state/snapshot → Computer 深拷贝 → 查询消费者;安装另有 Future、metrics 和结果对象形成临时峰值。然后回答:为什么不能用“包数 × 用户数”直接得到 user state 数量?为什么 snapshot 不一定复制所有字符串/对象?为什么 heap dump 必须区分长期对象和安装临时对象?
