前阵子线上版本发布后,群里被用户刷屏:“iPhone 没弹崩溃,但 App 退到桌面再点回来,居然重新加载了。” 打开数据一看,OOM率从 0.3% 一路涨到 1.2%。用户体感就是闪退、白屏、状态丢失——但崩溃平台上一个堆栈都没有。这就是 iOS OOM 治理最磨人的地方:它不是崩溃,却比崩溃更难排查。这篇文章我就把自己做 OOM 治理的完整思路、工具链和实际落地方案整理出来,希望能给正在做 iOS 性能优化、稳定性治理的同行一些参考。
1. 先搞清楚在治理什么:OOM 不等于崩溃
很多团队第一次接到 OOM 相关的工单时,第一反应是抓崩溃堆栈,结果发现平台上什么都没有。这里必须先纠正一个认知:iOS 的 OOM 和 crash 是两码事,治理思路完全不同。
1.1 Jetsam 是怎么判定“该杀你了”的
iOS 用内存压力作为核心调度依据。当一个 App 的物理内存占用过高,系统会触发 Jetsam 机制,进程被直接终止。你可以把 Jetsam 理解成一个大楼里的物业管理员:电梯、空调、人均用水量都有限,某户居民把水管开到最大,物业为了保护整栋楼的正常运转,会直接切断这户的水电,而不是先敲门商量。
关键点在这里:Jetsam 终止进程时不会给 App 任何回调,你的代码根本来不及执行。所以 OOM 没有常规的崩溃堆栈,甚至 App 内部都感知不到“我快被杀了”。这个特性决定了 OOM 治理必须依靠系统日志和事前的监控数据,而不是错误上报平台。
1.2 OOM 日志到底去哪找
虽然 App 自己不知道,但系统会把每次 JetSam 事件写入诊断日志。在 iPhone 上打开“设置 → 隐私与安全性 → 分析与改进 → 分析数据”,找前缀是 JetsamEvent 的文件,就能看到系统记录的详细情况。
要注意三个关键信息:
reason字段:如果是per-process-limit,说明是单个 App 触顶被杀;如果是vm-pageshortage或global,说明是整个系统内存紧张,你的 App 只是被选中牺牲的那个。两种原因的治理方向不一样。memlimit字段:这是系统给当前进程设置的内存上限,单位一般在日志里以页或字节形式出现,如果日志里标记了pageSize,可以用memlimit × pageSize反推具体字节数。rpages与footprint:进程被杀瞬间的物理内存占用。
iPhone 真机上还有一套更简便的途径:把设备连接到 Mac,在 Xcode 里打开“Window → Devices and Simulators → View Device Logs”,同样能看到 Jetsam 日志。实机上这个日志才是准的,模拟器上内存策略不一样,别浪费时间复现。
1.3 治理的其实是“footprint”,不是“allocated size”
很多初学者有个误区:以为 OOM 是“分配的内存太多”导致的,于是疯狂排查 malloc 分配总量。但系统在判定 OOM 时,关注的是 footprint——也就是进程中实际被映射并驻留在物理内存的页面数,等于你真实压给物理内存的重量。
比如你用 malloc 申请了 500MB,但大部分页面还没写入数据,footprint 可能只有几十MB;反过来,如果你有 500MB 的缓存对象被反复访问,这些内存都会真实落在物理页上,footprint 就很高。所以治理的重点是降低真实的物理内存占用峰值,而不是单纯减少虚拟内存分配。
理解这一点之后,再看下面的根因分类就顺理成章了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 常见的内存失控场景:我整理的三类主力
我治理过几个头部 App 的 OOM 问题,基本上所有线上 OOM 都可以归到这三类:
2.1 第一类:图片大户,解压后的位图最吃内存
这是 OOM 的第一大源头。一张普通的 2000×3000 像素照片,解压成位图后内存大小是 2000 × 3000 × 4 字节,约 24MB。听起来没多少?一个信息流页面,同时存在屏幕上和预加载的图片轻松超过 20 张,内存瞬间堆到 400-500MB。
UIImage 的加载方式影响也很大:
imageNamed:自带缓存机制,重复加载不走磁盘,但缓存的位图不释放,会增加 footprint;- `imag
