iOS 相机内存实测报告

双场景对照:内存组成与变化(含文件页全统计)· 申请分配效率 · 回收供给效率
iPhone 12 Pro · A14 · 6GBiOS 14.8 (xnu-7195.141.2)16KB 物理页越狱真机 root 实测2026-08-29
📖 术语速览

管线(pipeline)=相机数据通路:图像数据从传感器到屏幕流经的整条硬件/软件链路——传感器 → ISP → DMA → 缓冲池(内核wired 967MB) → mediaserverd(540MB) → IOSurface → backboardd(21MB) → 屏幕。Camera.app 只是这条链路的遥控器(壳 26MB)。文中"管线需求/管线就位/管线配置"均指这条链路:需求量由会话配置(分辨率/格式/帧深度)决定,与内存环境无关;就位 ~3s 是建链时间;管线属于媒体会话不属于进程——杀 App 不立即拆管线,故释放有异步尾巴。

wired:物理页锁定驻留(不压缩/不换出/不丢弃)——相机场景里是 ISP DMA 缓冲池,记在全局计数器而非任何进程账上。speculative:投机预读的干净文件页缓存——free 不足时最先被零成本丢弃。

⚡ 简要结论(TL;DR)——先看答案,再看论证
相机申请多少?哪些内存?

整机 ~1.0–1.6GB,四归属:内核 wired 967MB(55%,无主 DMA,不进任何进程账) + mediaserverd iokit_mapped ~540MB(40%)+ Camera 壳匿名 26–28MB(2%)+ 显示侧 ~21MB。两场景 wired 上涨差 <1%(62,3xx vs 62,391 页)——管线需求与内存环境无关,free 只剩 55MB 照样拿到 974MB。wired 属于"预览"不属于"启动"(冷启动 Δwired=0)。

启动多快?申请效率多快?

内存全部就位 ~3s,free=55MB 与 free=2.32GB 持平。分配原语极快:wire 与匿名同速(5.2 vs 5.0GB/s,3.01µs/页——"wired 分配慢"是误解);活体管线斜率 356–570MB/s,瞬时 ≥1.6GB/s——慢在管线编排(DART/驱动会话),不在 wire。

free 不足时如何供给?

水位驱动的流水线,不是预案。场景二极限局(free 55MB 供 974MB):后台压缩波抢跑 +474MB(相机启动前就发生) → 文件页瞬时逐出 295MB(speculative clean 页零 I/O 丢弃)→ 匿名压缩 695MB gross(突发 ~319MB/s)。freeze/jetsam 零触发,五 App 全存活——相机 1GB 停在页级腾挪,不需要进程级献祭。

回收效率与统计值合理性

回收成本阶梯:purgeable 丢弃 ≈0 < 文件页 clean 丢弃(≥295MB/s 零 I/O)< munlock 20GB/s < 压缩器 117–328MB/s < 驱动 teardown。杀相机:bulk 830–854MB <1s + 死会话压缩页直接摘除 223MB(不经解压)+ 尾 ~8s;存活 App 压缩页不回吐(单向阀)。所有计数器变化可由机制解释,无泄漏——回收机器是水位驱动的状态机:场景一 compressions +0 vs 场景二 +695MB。

§0实验设计:两个指定场景

场景一:后台干净场景二:五大 App 后台
前置操作killall 所有可杀 App;轻压触发文件页回收(40s)依次启动抖音、淘宝、京东、网易云音乐、bilibili(每 App ps 验证存活后下一个)
进入条件free > 2GB ✓(vm_stat 确认 152,560 页 = 2.32GB)free = 3,551 页 = 55MB(五 App 吃光水位)
操作序列uiopen 相机 → 进预览静置 30s → killall Camera → 观察释放 22s同左(同参数对照)
采样0.5s×130 点全计数器:vm_stat(free/wired/file-backed/anonymous/压缩器/speculative)+ Camera/mediaserverd 双进程 appscan同左
累计计数器三时点快照(启动前/预览稳态/杀后):reactivated/purged/compressions/decompressions/pageins/pageouts同左

Q1相机启动申请多少内存?系统供给多少?分别是哪些内存?

1.1 相机申请的内存(进程侧视角)

归属内存类型场景一实测场景二实测机理
内核(无主 wired)wired+62,3xx 页 ≈ 967MB+62,391 页 = 974MBISP DMA 缓冲池/驱动固件——kmem_alloc_contig 物理就位,不进任何进程 footprint,只有全局 wired 计数器可见
mediaserverdiokit_mapped 为主fp 261.1→604.2MB262.0→606.7MBAVFoundation 服务端——IOSurface 回映射记它的账
Camera 进程匿名 internalfp 26.2MBfp 28.3MB纯 App 壳(UI/按钮/权限对话框)
Camera 壳+msd 会话的匿名页anonymous(整机计数器)+422MB(45,321→72,322 页)先 +(唤醒)后 -367MB(转入压缩器)msd internal ~93MB + 壳 + UI 工作集
相机自身文件页file-backed+105MB 载入(pageins 125MB)同样载入 ~91–132MB(与逐出对冲)二进制/AVFoundation/CoreMedia/Metal shader 从磁盘载入
backboardd 等显示侧iokit_mapped~+21MB(早前普查)同左显示合成侧 surface 映射

关键不变量:管线需求与内存环境无关——两场景 wired 上涨差 <1%(62,3xx vs 62,391 页,≈967–974MB)。free 充足时拿 967MB,free 只剩 55MB 时照样拿到 974MB。

1.2 系统供给多少(整机视角的物理账)

供给来源场景一(free 2.32GB)场景二(free 55MB)
free 池直接支付全额支付(-97,945 页 = 1,530MB)-34,007 页 = 531MB(t=19 前后台压缩波已把 free 抬到 584MB)
文件页逐出(file-backed)0(零逐出——反向 +105MB 载入)瞬时 -295MB(≤1s)→ 回填 +91MB → 净 -204MB
 └ speculative 子集+54MB(预读新增)20,445→542 页 = -311MB 被清光
匿名页压缩转存0(compressions +0!全程零压缩)compressions +44,507 页 = 695MB 总压缩(stored +533MB 净)
purgeable 丢弃purged +291 页(5MB,微量)purged +1,911 页(30MB)
后台 App 主动压缩波(相机未启动时)n/at=7 +331MB、t=17 +143MB(进场即压,抢跑供给)
freezer / jetsam00——零击杀,五 App 全存活

场景二物理账闭环(杀相机瞬间):wired -830MB + 压缩器死会话页直接摘除释放物理 ~104MB ≈ free +940MB ✓。一台 free 只剩 55MB 的机器,先靠后台压缩波抢跑 +474MB,再靠文件页瞬时逐出 295MB + 匿名压缩 695MB 总量,供相机拿到 974MB wired,五个后台 App 一个都没死

Q2启动耗时多长?申请效率多快?free 不足时如何回收供给?

2.1 启动耗时(内存就位时间线)

里程碑场景一场景二
进程可见(pid 出现)≤1s≤1s
wired 主体到位(>90%)~2s(t=20→22)~2s(t=20→22)
mediaserverd fp 稳定(~605MB)~3s~3s
内存全部就位~3s~3s(不受影响!)

启动耗时的本质:内存侧全就位只需 ~3 秒,且在 free=55MB 的 starvation 条件下与 free=2.32GB 持平——回收腾页与管线分配在不同核上并行,互不等待。

2.2 申请分配效率(微基准 alloceff + 活体斜率)

微基准(256MB 档)
操作吞吐µs/16K页
VA 预留(mmap 不触碰)~瞬时
匿名 zero-fill 触碰5,025 MB/s3.11
mlock 冷 wire(fault+wire)5,189 MB/s3.01
mlock 热 wire(页已在)9,959 MB/s1.57
munlock 解 wire20,009 MB/s0.78
purgeable 触碰5,280 MB/s2.96
purgeable set VOLATILE19,024 MB/s0.82
purgeable set EMPTY~瞬时≈0
munmap31,166 MB/s
活体斜率(相机管线实测)
阶段速率
场景一 t=20→23:wired +889MB / 2.5s~356 MB/s
场景二 t=20→22:wired +855MB / 1.5s~570 MB/s
瞬时突发(0.5s 窗口,早前 0.4s 采样)≥1.6 GB/s
mediaserverd fp(+341MB / 2s)~170 MB/s
Camera 壳 footprint~2.4 MB/s(微不足道)

微基准 5GB/s vs 活体 356–570MB/s 的差距 = 管线配置开销(DART 映射、驱动会话协商、surface 池建立)——慢的不是 wire 原语,是管线编排

"wired 分配慢"是误解:冷 wire 3.01µs/页 与匿名 zero-fill 3.11µs/页同速。1GB wired 的内核机械时间只需 ~200ms。惰性申请(用到才 wire)没有任何性能代价。

2.3 free 不足时如何回收供给(场景二实录)

五 App 后台进场(相机还没启动!)
   │  t=7:  压缩器 stored 48,854→70,035 = +331MB 主动压缩波(free +437MB)
   │  t=17: 再 +143MB(free 抬到 584MB)——抢跑供给:后台 App 进后台即被压
   ▼ t=20 uiopen 相机
free 37,460→1,751(池耗尽)  wired 50,278→104,653
file-backed 129,293→110,410:≤1 秒逐出 295MB(speculative 20,445→542 清光)
   │
   ▼ t=22(~2s)
wired 112,513 主体到位   anon 120,005→100,210(-311MB 转入压缩器)
压缩器 stored 79,019→109,579(+479MB / 1.5s —— 突发 ~319MB/s)
   │
   ▼ t=25(~4s 全部就位)
file-backed 回填 +91MB(相机自身框架 pagein 中,pageins 累计 +8,483 页=132MB)
   │
   ▼ 稳态 30s:五 App 全存活(freeze=0 零击杀)
   │
   ▼ t=57 killall Camera
wired 112,907→57,457(bulk -830MB <1s); free +940MB
压缩器 stored 112,907→98,646(-223MB):死会话压缩页直接摘除(decompressions 仅 +194)
t=57→65: 尾巴 wired→49,396(驱动 teardown ~8s); 存活 App 的压缩页原样保留

供给是流水线不是预案:第一波甚至发生在相机启动之前(后台 App 进场即被主动压缩——抢跑);相机分配逼近水位时同步唤醒逐级加深——free 池 → 文件页逐出(clean 页零 I/O 丢弃)→ 匿名压缩 → freezer(未触及)→ jetsam(未触及)。相机 1GB 停在前三级:页级腾挪足够,进程级献祭不需要。

2.4 回收效率三张账

回收机制实测效率说明
文件页逐出(场景二)≥295MB/s 瞬时(≤1s 逐出 295MB);零 I/O 成本(clean 页直接丢)几乎全部来自 speculative 子集(投机预读的干净页)——最便宜的供给路
压缩器收纳(场景二)突发 ~319–328MB/s(1.5s 收 479MB);持续 ~117MB/s;压缩比 2.15:1compressions 累计 +695MB(gross,含churn),stored 净 +533MB
杀相机 bulk 释放742–854MB <1smunlock 微基准 20GB/s 说明解 wire 原语不是瓶颈——限速的是驱动 teardown 异步
死会话压缩页摘除223MB 瞬时(随 bulk)压缩器条目直接删除、不经 decompression(计数器仅 +194)——释放侧的隐藏通道
释放尾巴~5–8s 回到基线驱动/会话异步拆除;期间不阻塞新分配
存活 App 压缩页回吐≈0(单向阀)五 App 的压缩页杀后原样保留,恢复时按需解压(~12µs/页)
freezer / jetsam0 次相机独吃 1GB 不需要进程级回收(早前 5.4GB 冲击才逼出 freezer)

Q3各内存统计值变化情况与合理性分析

3.1 场景一全计数器时间线(后台干净,free 2.32GB)

t(s)   wired     free    filebk     anon   cmpstr   spec   cam_fp  msd_fp
  0    44,499  149,633  100,092  42,926   53,806   8,492      -       -   ← 基线(free 2.32GB)
 19    44,515  145,459  100,882  45,321   53,636  10,054      -       -   ← uiopen
 20    52,360  134,775  103,077  46,191   50,942  11,936   21.0   261.1   ← pid 可见
 21   101,479   76,667  103,772  54,535   50,326  12,083   26.6   572.8
 22   108,197   48,164  107,582  72,465   50,267  13,458   26.7   598.8   ← fb+105MB/anon+422MB 到位
 23   108,744   47,483  107,625  72,573   50,267  13,491   27.3   604.5
25~56  106,49x   50,0xx  107,6xx  72,3xx   50,25x  13,5xx   26.2   603.0   ← 稳态30s纹丝不动
 57    51,644  125,618  107,077  52,135   50,205  13,663      -       -   ← bulk -854MB <1s
 65+   44,613  133,500  107,294  50,994   50,195  13,677      -       -   ← 基线恢复(~8s)

累计计数器(预览 30s 窗口):reactivated +51、purged +291(5MB)、compressions +0(全程零压缩!)、decompressions +2,445(38MB,msd 唤醒旧压缩页)、pageins +8,038(125MB 磁盘载入)、pageouts 0。

3.2 场景二全计数器时间线(五 App 后台,free 55MB)

t(s)   wired     free    filebk     anon   cmpstr   spec   cam_fp  msd_fp
  0    51,526    3,551  129,194  152,383   48,854  20,391      -       -   ← 基线(free 55MB)
  7    50,352   28,023  129,230  129,415   70,035  20,410      -       -   ← 后台主动压缩波+331MB
 17    50,363   37,424  129,281  119,885   79,019  20,439      -       -   ← 再+143MB(相机未开!)
 20    55,906   33,215  129,323  118,537   78,999  20,435   20.1   262.0   ← uiopen
 21   104,653    1,751  110,410  121,985   78,980     542   27.5   538.2   ← fb 瞬时-295MB!
 22   112,513    2,750  114,473  100,210  109,579  2,117   28.3   602.8   ← anon压缩+479MB/1.5s
 25   112,669    3,453  116,235   96,526  113,356   2,881   28.1   606.6   ← fb回填+91MB,全部就位
28~56  110,60x   4,6xx   116,7xx   96,8xx  112,9xx  3,4xx   27.1   605.4   ← 稳态
 57    57,457   64,783  116,175   90,873   98,646  3,526      -       -   ← bulk -830MB + 死会话压缩页-223MB
 65+   49,352   74,185  116,530   89,270   98,518   3,617      -       -   ← 存活App压缩页保留

累计计数器(整个窗口):reactivated +55,158(862MB 工作集唤醒churn)、purged +1,911(30MB)、compressions +44,507(695MB 总压缩)、decompressions +866(14MB)、pageins +8,483(132MB,与场景一的 125MB 几乎一致)、pageouts +73(1MB)。杀后:decompressions +194、其余 ≈0。

3.3 文件页专节:一个计数器,两种角色

场景一(free 充足)场景二(free 枯竭)
file-backed 净变化+105MB(纯需求侧)-204MB(净供给方)
瞬时动作无逐出≤1s 逐出 295MB
speculative 子集+54MB(预读增加)20,445→542 清光(-311MB)
相机自身载入+105MB(pageins 125MB)+91MB 回填(pageins 132MB——逐出与载入同时进行
磁盘读(pageins)+8,038 页 = 125MB+8,483 页 = 132MB(几乎相同——载入需求恒定

文件页的双重角色:free 充足时文件页是需求(相机二进制/AVFoundation/CoreMedia/Metal shader 从磁盘载入 +105MB,零逐出);free 枯竭时它同时是需求与供给——一边逐出 295MB 旧文件页(几乎全是 speculative:投机预读的 clean 页,零 I/O 直接丢),一边载入 132MB 相机自己的文件页。文件缓存被"换血"而不是"清空"。这是 free 池之外的第二供给路,且比压缩器更便宜(无需 CPU 压缩,无需 I/O)。

为什么场景一 compressions +0 而场景二 +695MB:压缩器只在水位压力下工作。场景一 free 2.32GB,回收器全程休眠(甚至反向解压 38MB 唤醒 msd 旧工作集);场景二每一步分配都顶着水位线,压缩器全程满负荷。同一台机器,两种内存环境下回收机器的"开/关"状态完全不同——这正是"内存统计值合理性"要看的本质:统计值的变化方向由环境水位决定,而不是常数。

3.4 逐计数器合理性分析

统计值场景一变化场景二变化合理性判定
wired+62,3xx(967MB)+62,391(974MB)✅ 差 <1%——管线需求是会话配置的函数,与内存环境无关。峰值后小幅回落 ~2,000 页:驱动建立期的临时映射释放
free-1,530MB(全额支付)-531MB(t=19 起;此前后台波已抬 584MB)✅ 降幅 > wired 涨幅的部分 = fb 载入 105MB + anon 新增 422MB + spec 预读
file-backed+105MB(载入)净 -204MB(逐出 295 - 回填 91)✅ 一需求一供给——见 §3.3 专节。pageins 两场景几乎一致(125/132MB)证明载入需求恒定
anonymous+422MB(新增工作集)净 -367MB(转入压缩器)✅ 场景二 anon 下降量 ≈ stored 上升量的内容来源(695MB gross 含唤醒churn再压缩)
compressor stored-53MB(反向解压)+533MB 净(695MB gross)✅ 一收一放:场景一 msd 唤醒解压 38MB;场景二满负荷收纳。杀后场景二 -223MB 为死会话条目直接摘除(decompressions 仅 +194)——释放侧隐藏通道
speculative+54MB(预读)-311MB(清光)✅ 最便宜的牺牲品:clean、无引用、零成本丢弃——文件页供给的主力
reactivated+51(≈0)+862MB✅ 场景二后台 App 页被反复触碰唤醒(框架共享库/回调)——高压环境工作集churn的信号,也解释 gross compressions (695MB) > 净 stored (+533MB) 的差
purged+5MB+30MB✅ purgeable 丢弃量小且方向一致——相机场景非主力
pageins / pageouts125MB / 0132MB / 1MB✅ 磁盘读恒定(相机载入需求);几乎无写回(clean 页丢弃不需要写)
freeze/jetsam00✅ 相机 1GB 在页级腾挪能力内(文件页 295MB + 压缩器 695MB 容量)
杀后释放bulk 854MB<1s + 尾~8sbulk 830MB<1s + 死会话摘除 223MB + 尾~8s✅ 两场景一致;存活 App 压缩页保留(单向阀——回吐要付解压代价,系统选择"够用就好")

合理性总判:所有计数器变化方向均可由机制解释,无异常泄漏。文件页计数器在两场景的方向反转(+105 vs -204)与压缩器的开/关切换(0 vs 695MB)是本次复测最有价值的发现——它们证明 iOS 回收机器是按水位驱动的状态机,不是恒定速率的泵。

§4效率汇总:申请分配 × 回收

内存类型申请吞吐释放/回收方式释放吞吐/延迟相机场景的量
VA 预留~瞬时munmap31 GB/s
匿名(zero-fill)5.0 GB/s压缩器收纳~319–328MB/s 突发 / 117MB/s 持续(2.15:1)壳 26MB + 后台 App 大量
wired(用户 mlock)5.2 GB/s(冷)munlock20 GB/s
wired(内核管线)活体 356–570MB/s(瞬时≥1.6GB/s)驱动 teardownbulk <1s + 尾 ~8s967–974MB(两场景不变)
file-backed(供给)clean 页直接丢弃≥295MB/s 瞬时,零 I/O场景二净 -204MB
iokit_mapped跟随 surface 建立(~170MB/s)surface 生命周期跟随 mediaserverd~540MB
purgeable5.3 GB/s直接丢弃≈0 成本30MB(微量)

总纲:各内存类型申请效率高度一致(原语层 3µs/页量级,差异全在管线编排层);差异全在回收侧的"成本阶梯"——purgeable 丢弃 ≈0 成本 < 文件页 clean 丢弃(零 I/O,≥295MB/s)< munlock(20GB/s)< 压缩器收纳(117–328MB/s + CPU)< 驱动 teardown(异步尾巴)。相机选 wired 不是因为分配快,而是物理就位承诺(DMA 不能缺页);代价在释放侧(异步尾巴)——结构性豁免的代价是释放不能同步完成。

§5方法学与数据完整性

工具用途
appscanper-pid footprint/rss/internal/compressed(TASK_VM_INFO+purgeable 查询)
alloceff各内存类型分配/释放效率微基准(匿名/mlock wire/purgeable,256MB 档)
vm_stat + ps 采样器0.5s 间隔全计数器时间线(单次 vm_stat 捕获后多路 grep,zsh 后台循环,sed 提取 pid)
swapprobe轻压工具(场景一前置:逼出文件页回收把 free 抬过 2GB)

实测设备:iPhone 12 Pro(A14/6GB/iOS 14.8 越狱);源码锚点 xnu-7195.141.2。 主书:ios-memory-book.pages.dev(§5.5m/§5.5n/§5.5o)· 相机专题站:ios-camera-memory.pages.dev · 2026-08-29 · v2.0(新增文件页全统计/累计计数器/死会话摘除机制)