前言
理解 iOS 的内存分配机制是性能优化和问题排查的基础。当我们调用 malloc 或创建对象时,背后经历了复杂的内存管理流程。
本文将深入探讨 iOS 内存管理的三个核心层面:
- Malloc:用户态内存分配器
- Virtual Memory (VM):虚拟内存系统
- Dirty/Clean Memory:内存页的状态管理
一、iOS 内存架构概览
1.1 内存层次结构
┌─────────────────────────────────────────────────────────────────────┐
│ iOS 内存架构层次 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 应用层 (App) │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ [[NSObject alloc] init] ││ │
│ │ │ let obj = MyClass() ││ │
│ │ │ malloc(size) ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ libmalloc (用户态) │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ Nano Zone (< 256B) - 小对象,无锁分配 ││ │
│ │ │ Scalable Zone (256B-1MB) - 中等对象,分桶管理 ││ │
│ │ │ Large Zone (> 1MB) - 大对象,直接 VM 分配 ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 虚拟内存子系统 (VM) │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ vm_allocate() - 分配虚拟地址空间 ││ │
│ │ │ vm_map - 管理地址映射 ││ │
│ │ │ Pager - 处理缺页和换出 ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ XNU 内核 │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ 物理内存管理 (Physical Memory Manager) ││ │
│ │ │ 页表管理 (Page Table) ││ │
│ │ │ 内存压缩 (Memory Compressor) ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 物理内存 (RAM) │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ iPhone 15 Pro: 8GB ││ │
│ │ │ iPhone 15: 6GB ││ │
│ │ │ iPad Pro M2: 8GB/16GB ││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.2 内存类型分类
┌─────────────────────────────────────────────────────────────────────┐
│ iOS 内存类型分类 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 物理内存 (Physical) │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ Wired Memory (固定内存) │ │ │
│ │ │ • 内核代码和数据结构 │ │ │
│ │ │ • 不能被换出或压缩 │ │ │
│ │ │ • 通常 < 100MB │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ Active Memory (活跃内存) │ │ │
│ │ │ • 最近被访问的页面 │ │ │
│ │ │ • App 当前使用的内存 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ Inactive Memory (非活跃内存) │ │ │
│ │ │ • 最近未被访问的页面 │ │ │
│ │ │ • 可被回收或压缩 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ Compressed Memory (压缩内存) │ │ │
│ │ │ • 被内存压缩器压缩的页面 │ │ │
│ │ │ • iOS 不使用 swap,用压缩代替 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ Free Memory (空闲内存) │ │ │
│ │ │ • 未分配的物理页面 │ │ │
│ │ │ • 可立即使用 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 进程内存 (Per-Process) │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ Dirty Memory (脏内存) ⭐ │ │ │
│ │ │ • 进程修改过的内存 │ │ │
│ │ │ • 不能被简单释放,必须保留或压缩 │ │ │
│ │ │ • 主要优化目标! │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ Clean Memory (干净内存) │ │ │
│ │ │ • 可从磁盘重新加载的内存 │ │ │
│ │ │ • 如:mmap 的文件、__TEXT 段 │ │ │
│ │ │ • 内存紧张时可直接丢弃 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ Virtual Memory (虚拟内存) │ │ │
│ │ │ • 进程的地址空间 │ │ │
│ │ │ • 可能远大于物理内存 │ │ │
│ │ │ • 64位系统理论上可达 2^64 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
二、虚拟内存系统 (VM)
2.1 虚拟地址空间布局
┌─────────────────────────────────────────────────────────────────────┐
│ iOS 64位进程地址空间布局 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 0xFFFFFFFFFFFFFFFF ┌──────────────────────────────────────────┐ │
│ │ Kernel Space │ │
│ │ (用户态不可访问) │ │
│ 0xFFFFFC0000000000 ├──────────────────────────────────────────┤ │
│ │ │ │
│ │ 未使用区域 │ │
│ │ │ │
│ ├──────────────────────────────────────────┤ │
│ │ Stack (栈) │ │
│ │ ↓ 向下增长 │ │
│ │ 主线程栈约 1MB │ │
│ │ 子线程栈约 512KB │ │
│ ├──────────────────────────────────────────┤ │
│ │ │ │
│ │ 动态库/Framework │ │
│ │ (__TEXT, __DATA, __LINKEDIT) │ │
│ │ │ │
│ ├──────────────────────────────────────────┤ │
│ │ │ │
│ │ MALLOC_LARGE │ │
│ │ (> 1MB 的大分配) │ │
│ │ │ │
│ ├──────────────────────────────────────────┤ │
│ │ │ │
│ │ Heap (堆) │ │
│ │ ↑ 向上增长 │ │
│ │ Nano Zone / Scalable Zone │ │
│ │ │ │
│ ├──────────────────────────────────────────┤ │
│ │ __DATA (全局/静态变量) │ │
│ ├──────────────────────────────────────────┤ │
│ │ __TEXT (代码段) │ │
│ 0x0000000100000000 ├──────────────────────────────────────────┤ │
│ │ __PAGEZERO │ │
│ │ (保护区,触发空指针崩溃) │ │
│ 0x0000000000000000 └──────────────────────────────────────────┘ │
│ │
│ 说明: │
│ • 地址随 ASLR 随机化 │
│ • __PAGEZERO 大小为 4GB (64位) │
│ • 实际可用地址空间约 140TB (48位寻址) │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 VM Region 与 Page
┌─────────────────────────────────────────────────────────────────────┐
│ VM Region 与 Page 结构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 虚拟地址空间由多个 VM Region 组成: │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ VM Region 1: __TEXT │ │
│ │ ┌─────┬─────┬─────┬─────┬─────┬─────┐ │ │
│ │ │Page │Page │Page │Page │Page │Page │ 每页 16KB (ARM64) │ │
│ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 或 4KB (旧设备) │ │
│ │ └─────┴─────┴─────┴─────┴─────┴─────┘ │ │
│ │ 属性: 可读 + 可执行 │ │
│ │ 类型: Clean (可从磁盘重载) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ VM Region 2: __DATA │ │
│ │ ┌─────┬─────┬─────┬─────┐ │ │
│ │ │Page │Page │Page │Page │ │ │
│ │ │Dirty│Dirty│Clean│Dirty│ Dirty: 被修改过 │ │
│ │ └─────┴─────┴─────┴─────┘ Clean: 未修改(COW) │ │
│ │ 属性: 可读 + 可写 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ VM Region 3: MALLOC_SMALL │ │
│ │ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │Dirty│Dirty│Dirty│ Free│ Free│Dirty│Dirty│ Free│ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │
│ │ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │ │
│ │ 属性: 可读 + 可写 │ │
│ │ 类型: 全部为 Dirty (heap 分配) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ VM Region 4: MALLOC_LARGE │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 单个大对象 (> 1MB),独占多个连续页面 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ VM Region 5: Mapped File │ │
│ │ ┌─────┬─────┬─────┬─────┬─────┐ │ │
│ │ │Page │Page │Page │Page │Page │ │ │
│ │ │Resid│ --- │ --- │Resid│ --- │ Resident: 已加载到内存 │ │
│ │ └─────┴─────┴─────┴─────┴─────┘ ---: 未加载(需要时才加载) │ │
│ │ 类型: Clean (可从文件重载) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.3 页面状态与生命周期
┌─────────────────────────────────────────────────────────────────────┐
│ 内存页面状态转换 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ vm_allocate │ │
│ │ (分配虚拟地址) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Reserved │ │
│ │ (已保留) │ │
│ │ 无物理内存 │ │
│ └────────┬────────┘ │
│ │ │
│ │ 首次访问 (Page Fault) │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Committed │ │
│ │ (已提交) │ │
│ │ 分配物理页面 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Clean │ │ Dirty │ │ Dirty + │ │
│ │ (干净页面) │ │ (脏页面) │ │ Swapped Out │ │
│ │ 可直接丢弃 │ │ 已被修改 │ │ (已压缩) │ │
│ └────────┬────────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ │ 内存紧张 │ 内存紧张 │ 再次访问 │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Discarded │ │ Compressed │ │ Decompressed │ │
│ │ (已丢弃) │ │ (已压缩) │ │ (已解压) │ │
│ │ 可重新加载 │ │ 占用更少 │ │ 恢复使用 │ │
│ └─────────────────┘ └─────────────┘ └─────────────────┘ │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ iOS 内存压缩 (Memory Compressor): │
│ • iOS 不使用传统的 swap 到磁盘 │
│ • 使用内存压缩代替:压缩不活跃的 dirty pages │
│ • 压缩比通常 2:1 到 3:1 │
│ • 解压比磁盘 I/O 快得多 │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.4 VM 相关 API
c
#import <mach/mach.h>
// 1. 分配虚拟内存
kern_return_t vm_allocate(
vm_map_t target_task, // 目标任务,通常是 mach_task_self()
vm_address_t *address, // 输出:分配的地址
vm_size_t size, // 大小
int flags // VM_FLAGS_ANYWHERE 等
);
// 示例
void allocate_vm_memory(void) {
vm_address_t address = 0;
vm_size_t size = 1024 * 1024; // 1MB
kern_return_t kr = vm_allocate(
mach_task_self(),
&address,
size,
VM_FLAGS_ANYWHERE // 让系统选择地址
);
if (kr == KERN_SUCCESS) {
NSLog(@"分配成功: %p, 大小: %zu", (void *)address, size);
// 使用内存...
memset((void *)address, 0, size);
// 释放
vm_deallocate(mach_task_self(), address, size);
}
}
// 2. 修改内存保护属性
kern_return_t vm_protect(
vm_map_t target_task,
vm_address_t address,
vm_size_t size,
boolean_t set_maximum,
vm_prot_t new_protection // VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE
);
// 3. 查询内存区域信息
kern_return_t vm_region_64(
vm_map_t target_task,
vm_address_t *address,
vm_size_t *size,
vm_region_flavor_t flavor,
vm_region_info_t info,
mach_msg_type_number_t *infoCnt,
mach_port_t *object_name
);
// 示例:遍历进程所有内存区域
void enumerate_vm_regions(void) {
task_t task = mach_task_self();
vm_address_t address = 0;
vm_size_t size = 0;
while (1) {
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
vm_region_basic_info_data_64_t info;
mach_port_t object_name;
kern_return_t kr = vm_region_64(
task,
&address,
&size,
VM_REGION_BASIC_INFO_64,
(vm_region_info_t)&info,
&count,
&object_name
);
if (kr != KERN_SUCCESS) break;
NSLog(@"Region: %p - %p (%zu bytes)",
(void *)address,
(void *)(address + size),
size);
NSLog(@" Protection: %c%c%c",
(info.protection & VM_PROT_READ) ? 'r' : '-',
(info.protection & VM_PROT_WRITE) ? 'w' : '-',
(info.protection & VM_PROT_EXECUTE) ? 'x' : '-');
address += size;
}
}
三、Malloc 内存分配器
3.1 libmalloc 架构
┌─────────────────────────────────────────────────────────────────────┐
│ libmalloc 分配器架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ malloc(size) / calloc(n, size) / realloc(ptr, size) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Default Zone Dispatch │ │
│ │ │ │
│ │ 根据分配大小选择合适的 Zone: │ │
│ │ • size <= 256 bytes → Nano Zone │ │
│ │ • size <= 1MB → Scalable Zone │ │
│ │ • size > 1MB → Large Allocation (直接 VM) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┼──────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────────┐ │
│ │ Nano Zone │ │ Scalable │ │ Large │ │
│ │ │ │ Zone │ │ Allocation │ │
│ │ ≤256 bytes │ │ ≤1MB │ │ >1MB │ │
│ └────────────┘ └────────────┘ └────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────────┐ │
│ │ Nano Bands│ │ Magazine │ │ vm_allocate │ │
│ │ 无锁分配 │ │ per-CPU │ │ 直接分配 │ │
│ │ 快速回收 │ │ 分桶管理 │ │ 页对齐 │ │
│ └────────────┘ └────────────┘ └────────────────┘ │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ Zone 的特点: │
│ │
│ 【Nano Zone】 │
│ • 专门优化小对象分配 (≤ 256 bytes) │
│ • 使用 slot-based 分配,固定大小的槽位 │
│ • 每个线程有本地缓存,减少锁竞争 │
│ • 槽位大小: 16, 32, 48, 64, 80, ... 256 bytes │
│ │
│ 【Scalable Zone (Szone)】 │
│ • 处理中等大小分配 │
│ • 使用 Magazine 架构 (per-CPU) │
│ • Tiny: 16-1008 bytes │
│ • Small: 1-15 KB │
│ • 使用 free list 管理空闲块 │
│ │
│ 【Large Allocation】 │
│ • 大于 1MB 的分配 │
│ • 直接调用 vm_allocate │
│ • 每次分配独立的 VM region │
│ • 释放时直接返还给系统 │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2 Nano Zone 详解
┌─────────────────────────────────────────────────────────────────────┐
│ Nano Zone 内部结构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Nano Zone 使用固定大小的槽位 (Slot) 管理小对象: │
│ │
│ 槽位大小类别 (16 字节递增): │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ 16B │ 32B │ 48B │ 64B │ 80B │ ... │240B │256B │ │
│ │Slot0│Slot1│Slot2│Slot3│Slot4│ │Slot14│Slot15│ │
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │
│ │
│ 每个 Slot 类别有自己的 Band: │
│ │
│ Slot 0 (16 bytes) Band: │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │16B│16B│16B│16B│16B│16B│16B│16B│16B│16B│16B│16B│ │
│ │ U │ F │ U │ U │ F │ F │ F │ U │ F │ U │ U │ U │ │
│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │
│ U = Used (已使用) F = Free (空闲) │
│ │
│ Slot 1 (32 bytes) Band: │
│ ┌───────┬───────┬───────┬───────┬───────┬───────┐ │
│ │ 32B │ 32B │ 32B │ 32B │ 32B │ 32B │ │
│ │ U │ F │ U │ F │ U │ U │ │
│ └───────┴───────┴───────┴───────┴───────┴───────┘ │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ Nano Zone 的优化特点: │
│ │
│ 1. 无锁分配 (Lock-free) │
│ • 使用 CAS (Compare-And-Swap) 原子操作 │
│ • 避免线程阻塞 │
│ │
│ 2. 本地缓存 │
│ • 每个 CPU 有本地的空闲列表缓存 │
│ • 减少跨核竞争 │
│ │
│ 3. 快速释放 │
│ • 释放只需要将 slot 标记为空闲 │
│ • 加入本地空闲列表 │
│ │
│ 4. 位图管理 │
│ • 使用位图跟踪每个 slot 的使用状态 │
│ • 快速查找空闲 slot │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.3 Scalable Zone (Magazine) 详解
┌─────────────────────────────────────────────────────────────────────┐
│ Scalable Zone Magazine 架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Magazine 是 per-CPU 的分配池: │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Scalable Zone │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Magazine 0 │ │ Magazine 1 │ │ Magazine N │ │ │
│ │ │ (CPU 0) │ │ (CPU 1) │ │ (CPU N) │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Tiny Region Pool │ │ │
│ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │
│ │ │ │Region 1│ │Region 2│ │Region 3│ │Region 4│ │ │ │
│ │ │ │ 1MB │ │ 1MB │ │ 1MB │ │ 1MB │ │ │ │
│ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Small Region Pool │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ Region 1 │ │ Region 2 │ │ Region 3 │ │ │ │
│ │ │ │ 8MB │ │ 8MB │ │ 8MB │ │ │ │
│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ Tiny Allocation (16 - 1008 bytes): │
│ │
│ Region (1MB) 内部结构: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Metadata │ Block Area │ │
│ │ (位图等) │ ┌─────┬─────┬─────┬─────┬─────┬───────────┐ │ │
│ │ │ │Blk 1│Blk 2│Blk 3│Blk 4│Blk 5│ ... │ │ │
│ │ │ │ 64B │128B │ 64B │256B │ 64B │ │ │ │
│ │ │ └─────┴─────┴─────┴─────┴─────┴───────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Free List (空闲列表): │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Free1│───→│Free2│───→│Free3│───→│Free4│───→ NULL │
│ │ 64B │ │ 64B │ │ 64B │ │ 64B │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ 每个大小类别有自己的 free list │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.4 Malloc Zone API
objc
#import <malloc/malloc.h>
// 1. 获取默认 zone
malloc_zone_t *zone = malloc_default_zone();
// 2. 获取指针的分配信息
void *ptr = malloc(100);
size_t size = malloc_size(ptr);
NSLog(@"分配大小: %zu", size); // 可能大于请求的大小
// 3. 获取指针所属的 zone
malloc_zone_t *ptr_zone = malloc_zone_from_ptr(ptr);
NSLog(@"Zone: %s", malloc_get_zone_name(ptr_zone));
// 4. 遍历所有 zones
vm_address_t *zones;
unsigned int count;
kern_return_t kr = malloc_get_all_zones(mach_task_self(), NULL, &zones, &count);
if (kr == KERN_SUCCESS) {
for (unsigned int i = 0; i < count; i++) {
malloc_zone_t *z = (malloc_zone_t *)zones[i];
NSLog(@"Zone %d: %s", i, malloc_get_zone_name(z));
}
}
// 5. 打印 zone 统计信息
malloc_zone_print(zone, 0);
// 6. 打印所有内存分配信息
malloc_zone_statistics_t stats;
malloc_zone_statistics(zone, &stats);
NSLog(@"Blocks in use: %u", stats.blocks_in_use);
NSLog(@"Size in use: %zu", stats.size_in_use);
NSLog(@"Max size in use: %zu", stats.max_size_in_use);
// 7. 创建自定义 zone
malloc_zone_t *my_zone = malloc_create_zone(0, 0);
malloc_set_zone_name(my_zone, "MyCustomZone");
void *my_ptr = malloc_zone_malloc(my_zone, 256);
// 使用...
malloc_zone_free(my_zone, my_ptr);
// 释放整个 zone(释放所有分配的内存)
malloc_destroy_zone(my_zone);
3.5 查看内存分配详情
objc
// 使用 heap 命令行工具
// $ heap MyApp
// 使用 vmmap 命令行工具
// $ vmmap MyApp
// 代码中获取详细信息
void print_malloc_info(void) {
task_t task = mach_task_self();
// 获取任务内存信息
mach_task_basic_info_data_t info;
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
kern_return_t kr = task_info(task, MACH_TASK_BASIC_INFO,
(task_info_t)&info, &count);
if (kr == KERN_SUCCESS) {
NSLog(@"Virtual size: %llu MB", info.virtual_size / (1024 * 1024));
NSLog(@"Resident size: %llu MB", info.resident_size / (1024 * 1024));
}
// 获取更详细的内存统计
task_vm_info_data_t vm_info;
count = TASK_VM_INFO_COUNT;
kr = task_info(task, TASK_VM_INFO, (task_info_t)&vm_info, &count);
if (kr == KERN_SUCCESS) {
NSLog(@"Physical footprint: %llu MB",
vm_info.phys_footprint / (1024 * 1024));
NSLog(@"Compressed: %llu MB",
vm_info.compressed / (1024 * 1024));
NSLog(@"Internal: %llu MB",
vm_info.internal / (1024 * 1024));
NSLog(@"External: %llu MB",
vm_info.external / (1024 * 1024));
}
}
// 使用 MallocStackLogging 追踪分配
// 环境变量: MallocStackLogging=1
// 命令: malloc_history <pid> <address>
四、Dirty Memory 与 Clean Memory
4.1 核心概念
┌─────────────────────────────────────────────────────────────────────┐
│ Dirty Memory vs Clean Memory │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【Clean Memory - 干净内存】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 定义:可以从原始来源重新加载的内存 │ │
│ │ │ │
│ │ 来源: │ │
│ │ • Memory-mapped 文件(mmap) │ │
│ │ • __TEXT 段(代码) │ │
│ │ • Framework 的只读数据 │ │
│ │ • 图片的磁盘缓存 │ │
│ │ │ │
│ │ 特点: │ │
│ │ • 内存紧张时可以直接丢弃 │ │
│ │ • 需要时从磁盘重新加载 │ │
│ │ • 不会被压缩(没有意义,丢弃更快) │ │
│ │ • 不计入 App 的内存占用 (footprint) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【Dirty Memory - 脏内存】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 定义:被进程修改过、无法从其他来源恢复的内存 │ │
│ │ │ │
│ │ 来源: │ │
│ │ • malloc/calloc 分配的堆内存 │ │
│ │ • 全局变量(__DATA 段被修改的部分) │ │
│ │ • 栈内存 │ │
│ │ • Runtime 生成的数据(类信息、方法缓存等) │ │
│ │ • 解码后的图片像素数据 │ │
│ │ • 数据库缓存 │ │
│ │ │ │
│ │ 特点: │ │
│ │ • 不能简单丢弃,内容会丢失 │ │
│ │ • 内存紧张时被压缩到 Compressor │ │
│ │ • 是 App 内存优化的主要目标! │ │
│ │ • 直接计入 App 的内存占用 (footprint) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 【内存占用计算】 │
│ │
│ App Memory Footprint = Dirty Memory + Compressed Memory │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Footprint 组成: │ │
│ │ │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ │ Dirty Memory │ │ │
│ │ │ (未压缩的脏内存) │ │ │
│ │ ├────────────────────────────────────┤ │ │
│ │ │ Compressed │ │ │
│ │ │ (压缩后的脏内存) │ │ │
│ │ └────────────────────────────────────┘ │ │
│ │ │ │
│ │ 不计入 Footprint: │ │
│ │ • Clean Memory (可重载) │ │
│ │ • Shared Memory (与其他进程共享) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.2 Copy-On-Write (COW)
┌─────────────────────────────────────────────────────────────────────┐
│ Copy-On-Write 机制 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ COW 是优化内存使用的重要机制: │
│ │
│ 【初始状态 - Clean】 │
│ │
│ 进程 A 进程 B (fork from A) │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ 虚拟地址 0x1000 │ │ 虚拟地址 0x1000 │ │
│ │ ├── Page 1 ───────┼───────────┼──→ 物理页 X │ │
│ │ ├── Page 2 ───────┼───────────┼──→ 物理页 Y │ │
│ │ └── Page 3 ───────┼───────────┼──→ 物理页 Z │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ 所有页面标记为只读 (Read-Only) │
│ 两个进程共享相同的物理页面 │
│ 此时都是 Clean Memory │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 【写入时 - Dirty】 │
│ │
│ 进程 A 写入 Page 2: │
│ 1. 触发 Page Fault(页面是只读的) │
│ 2. 内核复制 Page 2 到新的物理页面 Y' │
│ 3. 更新进程 A 的页表指向 Y' │
│ 4. 新页面 Y' 标记为可写 │
│ 5. Page Y' 变成 Dirty │
│ │
│ 进程 A 进程 B │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ 虚拟地址 0x1000 │ │ 虚拟地址 0x1000 │ │
│ │ ├── Page 1 ───────┼───────────┼──→ 物理页 X (shared) │ │
│ │ ├── Page 2 ───────┼──→ 物理页 Y' (copied, dirty) │ │
│ │ │ │ ├──→ 物理页 Y (shared) │ │
│ │ └── Page 3 ───────┼───────────┼──→ 物理页 Z (shared) │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ 【iOS 中的 COW 应用】 │
│ │
│ 1. Framework/dylib 共享 │
│ • __TEXT 段在所有使用该库的 App 间共享 │
│ • __DATA 段初始共享,修改后变成 dirty │
│ │
│ 2. NSString/NSData 等不可变对象 │
│ • copy 操作可能只增加引用计数 │
│ • 真正修改时才复制 │
│ │
│ 3. Swift Copy-On-Write 容器 │
│ • Array, Dictionary, Set 等 │
│ • 共享存储直到修改 │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.3 查看 Dirty/Clean Memory
objc
#import <mach/mach.h>
// 获取详细的内存统计
void print_memory_stats(void) {
task_t task = mach_task_self();
// 使用 TASK_VM_INFO_PURGEABLE 获取更多信息
task_vm_info_data_t vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kr = task_info(task, TASK_VM_INFO,
(task_info_t)&vm_info, &count);
if (kr == KERN_SUCCESS) {
NSLog(@"=== Memory Statistics ===");
// 物理内存占用 (footprint)
NSLog(@"Physical footprint: %.2f MB",
vm_info.phys_footprint / (1024.0 * 1024.0));
// 内部内存 (dirty)
NSLog(@"Internal (dirty): %.2f MB",
vm_info.internal / (1024.0 * 1024.0));
// 外部内存 (file-backed, clean)
NSLog(@"External (file-backed): %.2f MB",
vm_info.external / (1024.0 * 1024.0));
// 压缩内存
NSLog(@"Compressed: %.2f MB",
vm_info.compressed / (1024.0 * 1024.0));
// 可清除内存
NSLog(@"Purgeable volatile: %.2f MB",
vm_info.purgeable_volatile_pmap / (1024.0 * 1024.0));
// 最大物理内存
NSLog(@"Resident size max: %.2f MB",
vm_info.resident_size_peak / (1024.0 * 1024.0));
}
}
// 遍历内存区域,分析 dirty/clean
void analyze_memory_regions(void) {
task_t task = mach_task_self();
vm_address_t address = 0;
vm_size_t size = 0;
uint32_t depth = 0;
uint64_t total_dirty = 0;
uint64_t total_clean = 0;
uint64_t total_swapped = 0;
while (1) {
vm_region_submap_info_data_64_t info;
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
kern_return_t kr = vm_region_recurse_64(
task, &address, &size, &depth,
(vm_region_recurse_info_t)&info, &count
);
if (kr != KERN_SUCCESS) break;
if (info.is_submap) {
depth++;
continue;
}
// 计算该区域的 dirty/clean 页面
uint64_t dirty_pages = info.pages_dirtied;
uint64_t clean_pages = info.pages_resident - info.pages_dirtied;
uint64_t swapped_pages = info.pages_swapped_out;
total_dirty += dirty_pages * vm_page_size;
total_clean += clean_pages * vm_page_size;
total_swapped += swapped_pages * vm_page_size;
address += size;
}
NSLog(@"=== Region Analysis ===");
NSLog(@"Total Dirty: %.2f MB", total_dirty / (1024.0 * 1024.0));
NSLog(@"Total Clean: %.2f MB", total_clean / (1024.0 * 1024.0));
NSLog(@"Total Compressed: %.2f MB", total_swapped / (1024.0 * 1024.0));
}
4.4 使用 vmmap 工具
bash
# 查看进程内存映射
$ vmmap <pid>
# 输出示例:
# ==== Non-writable regions for process 12345
# REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP]
# __TEXT 0000000100000000-0000000100800000 [ 8192K 4096K 0K 0K]
# __DATA 0000000100800000-0000000100900000 [ 1024K 512K 256K 0K]
# __LINKEDIT 0000000100900000-0000000100a00000 [ 1024K 256K 0K 0K]
# ==== Writable regions for process 12345
# REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP]
# MALLOC_NANO 0000600000000000-0000600008000000 [ 128M 64M 64M 0K]
# MALLOC_SMALL 0000000280000000-0000000284000000 [ 64M 32M 28M 4M]
# MALLOC_LARGE 0000000300000000-0000000310000000 [ 256M 200M 200M 56M]
# 查看内存汇总
$ vmmap -summary <pid>
# 输出示例:
# Physical footprint: 234.5M
# Physical footprint (peak): 345.6M
# === Dirty Memory ===
# MALLOC_NANO 64.0M
# MALLOC_SMALL 28.0M
# MALLOC_LARGE 200.0M
# __DATA 0.5M
# Stack 2.0M
# ...
# === Clean Memory ===
# __TEXT 128.0M
# __LINKEDIT 64.0M
# mapped file 50.0M
# ...
# 只看脏内存
$ vmmap -dirty <pid>
五、内存分配优化策略
5.1 减少 Dirty Memory
┌─────────────────────────────────────────────────────────────────────┐
│ 减少 Dirty Memory 的策略 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【策略 1: 使用内存映射文件代替 malloc】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 读取文件到堆内存 (dirty) │ │
│ │ NSData *data = [NSData dataWithContentsOfFile:path]; │ │
│ │ │ │
│ │ // ✅ 使用内存映射 (clean) │ │
│ │ NSData *data = [NSData dataWithContentsOfFile:path │ │
│ │ options:NSDataReadingMappedIfSafe│ │
│ │ error:nil]; │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 2: 延迟加载和按需分配】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 预先分配大数组 │ │
│ │ NSMutableArray *items = [[NSMutableArray alloc] │ │
│ │ initWithCapacity:10000]; │ │
│ │ │ │
│ │ // ✅ 按需增长 │ │
│ │ NSMutableArray *items = [NSMutableArray array]; │ │
│ │ // 只在需要时添加元素 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 3: 使用 Purgeable Memory】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // NSCache 使用可清除内存 │ │
│ │ NSCache *cache = [[NSCache alloc] init]; │ │
│ │ cache.totalCostLimit = 50 * 1024 * 1024; // 50MB │ │
│ │ │ │
│ │ // NSPurgeableData │ │
│ │ NSPurgeableData *data = [[NSPurgeableData alloc] │ │
│ │ initWithLength:1024*1024]; │ │
│ │ [data beginContentAccess]; │ │
│ │ // 使用数据... │ │
│ │ [data endContentAccess]; // 允许系统清除 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 4: 图片优化】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 直接加载大图 (dirty memory = width * height * 4) │ │
│ │ UIImage *image = [UIImage imageNamed:@"huge_image"]; │ │
│ │ │ │
│ │ // ✅ 下采样到实际显示大小 │ │
│ │ CGSize targetSize = imageView.bounds.size; │ │
│ │ UIImage *image = [self downsampledImageAtURL:url │ │
│ │ toSize:targetSize]; │ │
│ │ │ │
│ │ // ✅ 使用 ImageIO 进行下采样 │ │
│ │ CGImageSourceRef source = CGImageSourceCreateWithURL(...); │ │
│ │ NSDictionary *options = @{ │ │
│ │ (id)kCGImageSourceThumbnailMaxPixelSize: @(maxSize), │ │
│ │ (id)kCGImageSourceCreateThumbnailFromImageAlways: @YES │ │
│ │ }; │ │
│ │ CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex │ │
│ │ (source, 0, (__bridge CFDictionary) │ │
│ │ options); │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【策略 5: 释放不需要的内存】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ // 收到内存警告时 │ │
│ │ - (void)didReceiveMemoryWarning { │ │
│ │ [super didReceiveMemoryWarning]; │ │
│ │ [self.cache removeAllObjects]; │ │
│ │ [self.imageCache clearMemoryCache]; │ │
│ │ } │ │
│ │ │ │
│ │ // 或监听通知 │ │
│ │ [[NSNotificationCenter defaultCenter] │ │
│ │ addObserver:self │ │
│ │ selector:@selector(handleMemoryWarning) │ │
│ │ name:UIApplicationDidReceiveMemoryWarningNotification │ │
│ │ object:nil]; │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
5.2 避免内存碎片
objc
// 内存碎片问题演示
void demonstrate_fragmentation(void) {
// 1. 分配大量小对象
void *ptrs[1000];
for (int i = 0; i < 1000; i++) {
ptrs[i] = malloc(100); // 100 字节
}
// 2. 释放一半(交替释放)
for (int i = 0; i < 1000; i += 2) {
free(ptrs[i]);
ptrs[i] = NULL;
}
// 此时内存布局:
// [used][free][used][free][used][free]...
// 3. 尝试分配大块内存
void *big = malloc(50000); // 可能失败或需要新的 region
// 虽然有 500 * 100 = 50000 字节空闲
// 但因为不连续,无法用于大分配
}
// 减少碎片的策略
// 策略 1: 使用对象池
@interface ObjectPool : NSObject
@property (nonatomic, strong) NSMutableArray *availableObjects;
@property (nonatomic, assign) Class objectClass;
@end
@implementation ObjectPool
- (id)getObject {
if (self.availableObjects.count > 0) {
id obj = self.availableObjects.lastObject;
[self.availableObjects removeLastObject];
return obj;
}
return [[self.objectClass alloc] init];
}
- (void)returnObject:(id)obj {
// 重置对象状态
if ([obj respondsToSelector:@selector(reset)]) {
[obj performSelector:@selector(reset)];
}
[self.availableObjects addObject:obj];
}
@end
// 策略 2: 预分配固定大小的缓冲区
@interface FixedBufferPool : NSObject {
void *_buffer;
size_t _bufferSize;
size_t _blockSize;
NSMutableIndexSet *_freeBlocks;
}
@end
@implementation FixedBufferPool
- (instancetype)initWithBlockSize:(size_t)blockSize count:(NSUInteger)count {
self = [super init];
if (self) {
_blockSize = blockSize;
_bufferSize = blockSize * count;
_buffer = malloc(_bufferSize);
_freeBlocks = [NSMutableIndexSet indexSetWithIndexesInRange:
NSMakeRange(0, count)];
}
return self;
}
- (void *)allocBlock {
NSUInteger index = _freeBlocks.firstIndex;
if (index == NSNotFound) return NULL;
[_freeBlocks removeIndex:index];
return (char *)_buffer + (index * _blockSize);
}
- (void)freeBlock:(void *)block {
if (block < _buffer || block >= (char *)_buffer + _bufferSize) {
return; // 不属于这个池
}
NSUInteger index = ((char *)block - (char *)_buffer) / _blockSize;
[_freeBlocks addIndex:index];
}
@end
// 策略 3: 使用 autoreleasepool 及时释放
- (void)processLargeDataSet:(NSArray *)items {
for (NSInteger i = 0; i < items.count; i++) {
@autoreleasepool {
// 处理单个 item 产生的临时对象会在每次循环后释放
id item = items[i];
NSData *processed = [self process:item];
[self save:processed];
}
}
}
5.3 大内存分配优化
objc
// 大内存分配的最佳实践
// 1. 使用 vm_allocate 直接分配大块内存
void *allocate_large_buffer(size_t size) {
vm_address_t address = 0;
kern_return_t kr = vm_allocate(mach_task_self(), &address, size,
VM_FLAGS_ANYWHERE);
if (kr == KERN_SUCCESS) {
return (void *)address;
}
return NULL;
}
void free_large_buffer(void *buffer, size_t size) {
vm_deallocate(mach_task_self(), (vm_address_t)buffer, size);
}
// 2. 使用 mmap 进行文件映射
void *map_file(const char *path, size_t *size) {
int fd = open(path, O_RDONLY);
if (fd < 0) return NULL;
struct stat st;
fstat(fd, &st);
*size = st.st_size;
void *mapped = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (mapped == MAP_FAILED) return NULL;
// 建议内核预读
madvise(mapped, st.st_size, MADV_SEQUENTIAL);
return mapped;
}
void unmap_file(void *mapped, size_t size) {
munmap(mapped, size);
}
// 3. 使用 dispatch_data 处理大数据
- (void)processLargeFile:(NSURL *)fileURL {
// dispatch_data 可以引用数据而不复制
dispatch_data_t data = dispatch_data_create(buffer, length,
queue,
DISPATCH_DATA_DESTRUCTOR_FREE);
// 可以高效地拼接
dispatch_data_t combined = dispatch_data_create_concat(data1, data2);
// 可以创建子范围而不复制
dispatch_data_t subdata = dispatch_data_create_subrange(data, offset, length);
}
// 4. 处理图片时避免内存峰值
- (UIImage *)loadLargeImage:(NSURL *)url targetSize:(CGSize)targetSize {
// 使用 ImageIO 流式加载和下采样
CGImageSourceRef source = CGImageSourceCreateWithURL(
(__bridge CFURLRef)url, NULL);
if (!source) return nil;
// 获取原始尺寸
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
NSNumber *width = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
NSNumber *height = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
CFRelease(properties);
// 计算缩放比例
CGFloat scale = MIN(targetSize.width / width.floatValue,
targetSize.height / height.floatValue);
CGFloat maxDimension = MAX(width.floatValue, height.floatValue) * scale;
// 创建缩略图选项
NSDictionary *options = @{
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
(id)kCGImageSourceThumbnailMaxPixelSize: @(maxDimension),
(id)kCGImageSourceShouldCacheImmediately: @YES
};
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(
source, 0, (__bridge CFDictionaryRef)options);
CFRelease(source);
if (!thumbnail) return nil;
UIImage *image = [UIImage imageWithCGImage:thumbnail];
CGImageRelease(thumbnail);
return image;
}
六、内存监控与调试
6.1 实时内存监控
objc
#import <mach/mach.h>
#import <os/proc.h>
@interface MemoryMonitor : NSObject
+ (instancetype)shared;
- (void)startMonitoring;
- (void)stopMonitoring;
@end
@implementation MemoryMonitor {
dispatch_source_t _timer;
uint64_t _lastFootprint;
}
+ (instancetype)shared {
static MemoryMonitor *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[MemoryMonitor alloc] init];
});
return instance;
}
- (void)startMonitoring {
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(0, 0));
dispatch_source_set_timer(_timer,
dispatch_time(DISPATCH_TIME_NOW, 0),
1 * NSEC_PER_SEC, // 每秒
0);
__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(_timer, ^{
[weakSelf collectMemoryInfo];
});
dispatch_resume(_timer);
}
- (void)stopMonitoring {
if (_timer) {
dispatch_source_cancel(_timer);
_timer = nil;
}
}
- (void)collectMemoryInfo {
MemoryInfo info = [self getMemoryInfo];
// 检测内存增长
if (_lastFootprint > 0 && info.footprint > _lastFootprint + 10 * 1024 * 1024) {
NSLog(@"⚠️ 内存增长 %.2f MB",
(info.footprint - _lastFootprint) / (1024.0 * 1024.0));
[self captureMemorySnapshot];
}
_lastFootprint = info.footprint;
// 检测内存压力
if (info.footprint > [self getMemoryLimit] * 0.8) {
NSLog(@"🔴 内存使用超过 80%%,当前: %.2f MB",
info.footprint / (1024.0 * 1024.0));
[[NSNotificationCenter defaultCenter]
postNotificationName:@"MemoryPressureWarning" object:nil];
}
// 定期输出内存信息
static int counter = 0;
if (++counter % 10 == 0) { // 每 10 秒输出一次
[self logMemoryInfo:info];
}
}
typedef struct {
uint64_t footprint;
uint64_t footprintPeak;
uint64_t physical;
uint64_t internal;
uint64_t compressed;
uint64_t purgeable;
} MemoryInfo;
- (MemoryInfo)getMemoryInfo {
MemoryInfo info = {0};
task_vm_info_data_t vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kr = task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&vm_info, &count);
if (kr == KERN_SUCCESS) {
info.footprint = vm_info.phys_footprint;
info.footprintPeak = vm_info.ledger_phys_footprint_peak;
info.physical = vm_info.resident_size;
info.internal = vm_info.internal;
info.compressed = vm_info.compressed;
info.purgeable = vm_info.purgeable_volatile_pmap;
}
return info;
}
- (uint64_t)getMemoryLimit {
// iOS 设备的内存限制大约是物理内存的 50-70%
// 可以通过 os_proc_available_memory() 获取可用内存
if (@available(iOS 13.0, *)) {
return os_proc_available_memory();
}
// 备用方案:根据设备型号估算
size_t size = sizeof(uint64_t);
uint64_t memsize;
sysctlbyname("hw.memsize", &memsize, &size, NULL, 0);
return memsize * 0.5; // 约 50%
}
- (void)logMemoryInfo:(MemoryInfo)info {
NSLog(@"=== Memory Status ===");
NSLog(@"Footprint: %.2f MB (Peak: %.2f MB)",
info.footprint / (1024.0 * 1024.0),
info.footprintPeak / (1024.0 * 1024.0));
NSLog(@"Internal (Dirty): %.2f MB",
info.internal / (1024.0 * 1024.0));
NSLog(@"Compressed: %.2f MB",
info.compressed / (1024.0 * 1024.0));
NSLog(@"Purgeable: %.2f MB",
info.purgeable / (1024.0 * 1024.0));
NSLog(@"Available: %.2f MB",
[self getMemoryLimit] / (1024.0 * 1024.0));
}
- (void)captureMemorySnapshot {
// 捕获当前内存快照用于分析
NSMutableDictionary *snapshot = [NSMutableDictionary dictionary];
// 记录 malloc zones 信息
vm_address_t *zones;
unsigned int zoneCount;
kern_return_t kr = malloc_get_all_zones(mach_task_self(), NULL,
&zones, &zoneCount);
if (kr == KERN_SUCCESS) {
for (unsigned int i = 0; i < zoneCount; i++) {
malloc_zone_t *zone = (malloc_zone_t *)zones[i];
malloc_statistics_t stats;
malloc_zone_statistics(zone, &stats);
NSString *zoneName = @(malloc_get_zone_name(zone) ?: "Unknown");
snapshot[zoneName] = @{
@"blocks_in_use": @(stats.blocks_in_use),
@"size_in_use": @(stats.size_in_use),
@"max_size_in_use": @(stats.max_size_in_use),
@"size_allocated": @(stats.size_allocated)
};
}
}
// 保存快照
NSString *path = [NSTemporaryDirectory()
stringByAppendingPathComponent:
[NSString stringWithFormat:@"memory_snapshot_%@.plist",
@([[NSDate date] timeIntervalSince1970])]];
[snapshot writeToFile:path atomically:YES];
NSLog(@"Memory snapshot saved to: %@", path);
}
@end
6.2 内存泄漏检测
objc
// 简易的内存泄漏检测器
@interface LeakDetector : NSObject
+ (void)trackAllocation:(id)object;
+ (void)checkLeaks;
@end
@implementation LeakDetector
static NSMapTable *sTrackedObjects;
static NSMutableDictionary *sAllocationStacks;
static dispatch_queue_t sQueue;
+ (void)initialize {
if (self == [LeakDetector class]) {
// 使用 weak 引用,对象释放后自动移除
sTrackedObjects = [NSMapTable weakToStrongObjectsMapTable];
sAllocationStacks = [NSMutableDictionary dictionary];
sQueue = dispatch_queue_create("com.app.leak-detector", DISPATCH_QUEUE_SERIAL);
}
}
+ (void)trackAllocation:(id)object {
if (!object) return;
// 获取分配时的调用栈
NSArray *stack = [NSThread callStackSymbols];
NSString *key = [NSString stringWithFormat:@"%p", object];
dispatch_async(sQueue, ^{
[sTrackedObjects setObject:NSStringFromClass([object class])
forKey:object];
sAllocationStacks[key] = stack;
});
}
+ (void)checkLeaks {
dispatch_async(sQueue, ^{
NSLog(@"=== Checking for potential leaks ===");
NSUInteger count = 0;
for (id object in sTrackedObjects) {
NSString *className = [sTrackedObjects objectForKey:object];
NSString *key = [NSString stringWithFormat:@"%p", object];
NSArray *stack = sAllocationStacks[key];
NSLog(@"⚠️ Potential leak: %@ at %@", className, key);
if (stack.count > 5) {
NSLog(@"Allocation stack:");
for (int i = 2; i < MIN(8, stack.count); i++) {
NSLog(@" %@", stack[i]);
}
}
count++;
}
NSLog(@"Total potential leaks: %lu", (unsigned long)count);
});
}
@end
// 使用 Method Swizzling 自动追踪
@implementation NSObject (LeakTracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只在 Debug 模式开启
#ifdef DEBUG
[self swizzleAllocForLeakTracking];
#endif
});
}
+ (void)swizzleAllocForLeakTracking {
Class metaClass = object_getClass(self);
SEL originalSelector = @selector(alloc);
SEL swizzledSelector = @selector(tracked_alloc);
Method originalMethod = class_getClassMethod(self, originalSelector);
IMP swizzledIMP = imp_implementationWithBlock(^id(id self) {
id object = ((id (*)(id, SEL))method_getImplementation(originalMethod))
(self, originalSelector);
// 只追踪特定前缀的类
NSString *className = NSStringFromClass([object class]);
if ([className hasPrefix:@"MY"] || [className hasPrefix:@"App"]) {
[LeakDetector trackAllocation:object];
}
return object;
});
class_replaceMethod(metaClass, originalSelector, swizzledIMP,
method_getTypeEncoding(originalMethod));
}
@end
6.3 Instruments 内存分析
┌─────────────────────────────────────────────────────────────────────┐
│ Instruments 内存分析工具 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【1. Allocations】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 用途: │ │
│ │ • 追踪所有内存分配 │ │
│ │ • 分析内存增长趋势 │ │
│ │ • 查看分配的调用栈 │ │
│ │ │ │
│ │ 关键指标: │ │
│ │ • All Heap Allocations: 所有堆分配 │ │
│ │ • All Anonymous VM: 匿名虚拟内存 │ │
│ │ • Persistent Bytes: 持久存在的字节数 │ │
│ │ • # Persistent: 持久对象数量 │ │
│ │ • # Transient: 临时对象数量 │ │
│ │ │ │
│ │ 技巧: │ │
│ │ • 使用 Mark Generation 标记时间点 │ │
│ │ • 对比不同 Generation 之间的增量 │ │
│ │ • 筛选特定类名查看分配 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【2. Leaks】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 用途: │ │
│ │ • 检测内存泄漏 │ │
│ │ • 分析循环引用 │ │
│ │ │ │
│ │ 原理: │ │
│ │ • 定期扫描堆内存 │ │
│ │ • 查找没有任何引用指向的对象 │ │
│ │ │ │
│ │ 注意: │ │
│ │ • 只能检测真正的"泄漏"(无引用的对象) │ │
│ │ • 不能检测"逻辑泄漏"(有引用但不再需要) │ │
│ │ • 循环引用不一定被检测为泄漏 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【3. VM Tracker】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 用途: │ │
│ │ • 查看虚拟内存区域 │ │
│ │ • 分析 Dirty/Clean 内存分布 │ │
│ │ • 追踪内存映射 │ │
│ │ │ │
│ │ 关键列: │ │
│ │ • Dirty Size: 脏内存大小 │ │
│ │ • Swapped Size: 压缩内存大小 │ │
│ │ • Resident Size: 驻留内存大小 │ │
│ │ • Virtual Size: 虚拟内存大小 │ │
│ │ │ │
│ │ 常见 Region 类型: │ │
│ │ • MALLOC_*: malloc 分配的内存 │ │
│ │ • VM_ALLOCATE: vm_allocate 分配的内存 │ │
│ │ • __TEXT/__DATA: 代码和数据段 │ │
│ │ • mapped file: 内存映射文件 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 【4. Memory Graph Debugger (Xcode)】 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 位置:Debug → View Debugging → Capture Memory Graph │ │
│ │ │ │
│ │ 功能: │ │
│ │ • 可视化对象引用关系 │ │
│ │ • 自动检测循环引用 │ │
│ │ • 导出 .memgraph 文件供后续分析 │ │
│ │ │ │
│ │ 使用 vmmap 分析 memgraph: │ │
│ │ $ vmmap --summary App.memgraph │ │
│ │ $ leaks --outputGraph=leaks.memgraph App.memgraph │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
6.4 命令行工具
bash
# 1. vmmap - 查看进程内存映射
$ vmmap <pid>
$ vmmap --summary <pid>
$ vmmap --dirty <pid>
# 输出示例:
# Physical footprint: 234.5M
# Physical footprint (peak): 345.6M
#
# === Dirty Memory ===
# MALLOC zones: 156.8M
# MALLOC_TINY 12.3M
# MALLOC_SMALL 45.6M
# MALLOC_LARGE 98.9M
# MALLOC guard edges: 0.1M
# STACK GUARD: 8.0K
# Stack: 2.5M
# __DATA 1.2M
# ...
# 2. heap - 分析堆内存
$ heap <pid>
$ heap --sortBySize <pid>
$ heap --addresses <pid>
# 输出示例:
# Process 12345: 15234 nodes malloced for 123456 KB
#
# Count Bytes Average Class Name
# ----- ----- ------- ----------
# 5000 2000000 400.0 NSMutableDictionary
# 3000 480000 160.0 NSString
# 2000 320000 160.0 UIView
# ...
# 3. leaks - 检测内存泄漏
$ leaks <pid>
$ leaks --list <pid>
$ leaks --outputGraph=leaks.memgraph <pid>
# 4. malloc_history - 查看分配历史
# 需要先设置环境变量 MallocStackLogging=1
$ malloc_history <pid> <address>
# 5. footprint - 查看内存占用
$ footprint <pid>
# 输出示例:
# dirty compressed reclaimable wired process
# ------- ---------- ----------- ----- -------
# 156.8M 23.4M 0K 0K MyApp [12345]
# 6. memory_pressure - 模拟内存压力
$ memory_pressure -l critical # 模拟临界内存压力
$ memory_pressure -l warn # 模拟警告级别
七、内存优化实战案例
7.1 图片内存优化
objc
@interface ImageMemoryOptimizer : NSObject
// 1. 下采样加载图片
+ (UIImage *)downsampledImageWithURL:(NSURL *)url
targetSize:(CGSize)targetSize
scale:(CGFloat)scale;
// 2. 预取和缓存
+ (void)prefetchImagesAtURLs:(NSArray<NSURL *> *)urls
targetSize:(CGSize)targetSize;
// 3. 释放图片内存
+ (void)releaseImageCache;
@end
@implementation ImageMemoryOptimizer
+ (UIImage *)downsampledImageWithURL:(NSURL *)url
targetSize:(CGSize)targetSize
scale:(CGFloat)scale {
// 创建图片源
NSDictionary *sourceOptions = @{
(id)kCGImageSourceShouldCache: @NO // 不缓存完整图片
};
CGImageSourceRef source = CGImageSourceCreateWithURL(
(__bridge CFURLRef)url,
(__bridge CFDictionaryRef)sourceOptions
);
if (!source) return nil;
// 计算最大像素尺寸
CGFloat maxPixelSize = MAX(targetSize.width, targetSize.height) * scale;
// 创建缩略图选项
NSDictionary *downsampleOptions = @{
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
(id)kCGImageSourceShouldCacheImmediately: @YES,
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
(id)kCGImageSourceThumbnailMaxPixelSize: @(maxPixelSize)
};
// 创建下采样图片
CGImageRef downsampledImage = CGImageSourceCreateThumbnailAtIndex(
source,
0,
(__bridge CFDictionaryRef)downsampleOptions
);
CFRelease(source);
if (!downsampledImage) return nil;
// 创建 UIImage
UIImage *result = [UIImage imageWithCGImage:downsampledImage
scale:scale
orientation:UIImageOrientationUp];
CGImageRelease(downsampledImage);
return result;
}
// 计算图片内存大小
+ (size_t)memorySizeForImageSize:(CGSize)size scale:(CGFloat)scale {
// RGBA 每像素 4 字节
return size.width * scale * size.height * scale * 4;
}
// 异步加载图片
+ (void)loadImageAsync:(NSURL *)url
targetSize:(CGSize)targetSize
completion:(void (^)(UIImage *))completion {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
@autoreleasepool {
UIImage *image = [self downsampledImageWithURL:url
targetSize:targetSize
scale:UIScreen.mainScreen.scale];
dispatch_async(dispatch_get_main_queue(), ^{
completion(image);
});
}
});
}
// 使用 NSCache 缓存
static NSCache *sImageCache;
+ (NSCache *)imageCache {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sImageCache = [[NSCache alloc] init];
sImageCache.countLimit = 100;
sImageCache.totalCostLimit = 50 * 1024 * 1024; // 50MB
// 监听内存警告
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
[sImageCache removeAllObjects];
}];
});
return sImageCache;
}
+ (UIImage *)cachedImageForURL:(NSURL *)url size:(CGSize)size {
NSString *key = [NSString stringWithFormat:@"%@_%@",
url.absoluteString, NSStringFromCGSize(size)];
UIImage *cached = [[self imageCache] objectForKey:key];
if (cached) return cached;
UIImage *image = [self downsampledImageWithURL:url
targetSize:size
scale:UIScreen.mainScreen.scale];
if (image) {
NSUInteger cost = [self memorySizeForImageSize:image.size
scale:image.scale];
[[self imageCache] setObject:image forKey:key cost:cost];
}
return image;
}
@end
7.2 大数据处理优化
objc
@interface LargeDataProcessor : NSObject
// 流式处理大文件
+ (void)processLargeFile:(NSURL *)fileURL
chunkSize:(size_t)chunkSize
processor:(void (^)(NSData *chunk, BOOL *stop))processor;
// 分批处理大数组
+ (void)processBatchedArray:(NSArray *)array
batchSize:(NSUInteger)batchSize
processor:(void (^)(NSArray *batch))processor;
@end
@implementation LargeDataProcessor
+ (void)processLargeFile:(NSURL *)fileURL
chunkSize:(size_t)chunkSize
processor:(void (^)(NSData *chunk, BOOL *stop))processor {
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:fileURL.path];
if (!handle) return;
BOOL stop = NO;
while (!stop) {
@autoreleasepool {
NSData *chunk = [handle readDataOfLength:chunkSize];
if (chunk.length == 0) break;
processor(chunk, &stop);
}
}
[handle closeFile];
}
+ (void)processBatchedArray:(NSArray *)array
batchSize:(NSUInteger)batchSize
processor:(void (^)(NSArray *batch))processor {
NSUInteger count = array.count;
NSUInteger processed = 0;
while (processed < count) {
@autoreleasepool {
NSUInteger remaining = count - processed;
NSUInteger currentBatchSize = MIN(batchSize, remaining);
NSRange range = NSMakeRange(processed, currentBatchSize);
NSArray *batch = [array subarrayWithRange:range];
processor(batch);
processed += currentBatchSize;
}
}
}
// 使用 mmap 处理大文件(不占用 dirty memory)
+ (void)processFileWithMmap:(NSURL *)fileURL
processor:(void (^)(const void *data, size_t length))processor {
int fd = open(fileURL.path.UTF8String, O_RDONLY);
if (fd < 0) return;
struct stat st;
if (fstat(fd, &st) != 0) {
close(fd);
return;
}
size_t length = st.st_size;
// 映射文件到内存(这是 clean memory)
void *mapped = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (mapped == MAP_FAILED) return;
// 建议内核优化读取
madvise(mapped, length, MADV_SEQUENTIAL);
// 处理数据
processor(mapped, length);
// 解除映射
munmap(mapped, length);
}
// JSON 流式解析
+ (void)parseJSONStream:(NSInputStream *)stream
processor:(void (^)(id object, BOOL *stop))processor {
[stream open];
BOOL stop = NO;
while (stream.hasBytesAvailable && !stop) {
@autoreleasepool {
NSError *error;
id object = [NSJSONSerialization JSONObjectWithStream:stream
options:0
error:&error];
if (object) {
processor(object, &stop);
} else if (error) {
NSLog(@"JSON parse error: %@", error);
break;
}
}
}
[stream close];
}
@end
// 使用示例
- (void)processLargeJSONFile:(NSURL *)fileURL {
// 不要这样做:一次性加载全部数据
// NSData *data = [NSData dataWithContentsOfFile:fileURL.path];
// id json = [NSJSONSerialization JSONObjectWithData:data ...];
// 使用流式处理
NSInputStream *stream = [NSInputStream inputStreamWithURL:fileURL];
[LargeDataProcessor parseJSONStream:stream processor:^(id object, BOOL *stop) {
// 处理每个 JSON 对象
[self processJSONObject:object];
// 检查内存压力
if ([self isMemoryPressureHigh]) {
*stop = YES;
}
}];
}
7.3 缓存策略优化
objc
@interface MemoryAwareCache<KeyType, ObjectType> : NSObject
@property (nonatomic, assign) NSUInteger countLimit;
@property (nonatomic, assign) NSUInteger totalCostLimit;
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)cost;
- (ObjectType)objectForKey:(KeyType)key;
- (void)removeObjectForKey:(KeyType)key;
- (void)removeAllObjects;
@end
@implementation MemoryAwareCache {
NSCache *_cache;
NSMutableOrderedSet *_accessOrder;
NSMutableDictionary *_costs;
dispatch_queue_t _queue;
uint64_t _currentCost;
}
- (instancetype)init {
self = [super init];
if (self) {
_cache = [[NSCache alloc] init];
_cache.delegate = (id<NSCacheDelegate>)self;
_accessOrder = [NSMutableOrderedSet orderedSet];
_costs = [NSMutableDictionary dictionary];
_queue = dispatch_queue_create("com.app.cache", DISPATCH_QUEUE_SERIAL);
_countLimit = 100;
_totalCostLimit = 50 * 1024 * 1024;
// 监听内存警告
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
// 监听后台进入
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
return self;
}
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)cost {
if (!obj || !key) return;
dispatch_async(_queue, ^{
// 检查是否需要淘汰
while (self->_currentCost + cost > self.totalCostLimit &&
self->_accessOrder.count > 0) {
[self evictLeastRecentlyUsed];
}
// 移除旧值
id oldCost = self->_costs[key];
if (oldCost) {
self->_currentCost -= [oldCost unsignedIntegerValue];
[self->_accessOrder removeObject:key];
}
// 添加新值
[self->_cache setObject:obj forKey:key cost:cost];
self->_costs[key] = @(cost);
[self->_accessOrder addObject:key];
self->_currentCost += cost;
});
}
- (id)objectForKey:(id)key {
if (!key) return nil;
__block id result = nil;
dispatch_sync(_queue, ^{
result = [self->_cache objectForKey:key];
if (result) {
// 更新访问顺序
[self->_accessOrder removeObject:key];
[self->_accessOrder addObject:key];
}
});
return result;
}
- (void)evictLeastRecentlyUsed {
if (_accessOrder.count == 0) return;
id key = _accessOrder.firstObject;
NSNumber *cost = _costs[key];
[_cache removeObjectForKey:key];
[_accessOrder removeObject:key];
[_costs removeObjectForKey:key];
if (cost) {
_currentCost -= cost.unsignedIntegerValue;
}
}
- (void)handleMemoryWarning {
dispatch_async(_queue, ^{
// 清除一半的缓存
NSUInteger targetCount = self->_accessOrder.count / 2;
while (self->_accessOrder.count > targetCount) {
[self evictLeastRecentlyUsed];
}
});
}
- (void)handleEnterBackground {
dispatch_async(_queue, ^{
// 进入后台时清除所有缓存
[self->_cache removeAllObjects];
[self->_accessOrder removeAllObjects];
[self->_costs removeAllObjects];
self->_currentCost = 0;
});
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
八、iOS 内存限制与 Jetsam
8.1 Jetsam 机制
┌─────────────────────────────────────────────────────────────────────┐
│ iOS Jetsam 内存管理 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Jetsam 是 iOS 的内存压力处理机制: │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 系统内存状态 │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 正常状态 │ │ │
│ │ │ • 有充足的空闲内存 │ │ │
│ │ │ • 所有 App 正常运行 │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ ↓ 内存使用增加 │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 警告状态 (Warning) │ │ │
│ │ │ • 发送 didReceiveMemoryWarning 通知 │ │ │
│ │ │ • App 应该释放可恢复的资源 │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ ↓ 内存继续紧张 │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 临界状态 (Critical) │ │ │
│ │ │ • 开始终止后台 App │ │ │
│ │ │ • 按优先级和内存占用排序 │ │ │
│ │ │ • 后台 App 被 Jetsam kill │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ ↓ 前台 App 超限 │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ OOM Kill (Out of Memory) │ │ │
│ │ │ • 前台 App 超过内存限制 │ │ │
│ │ │ • 被 Jetsam 强制终止 │ │ │
│ │ │ • 产生 FOOM (Foreground OOM) 崩溃 │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ════════════════════════════════════════════════════════════════ │
│ │
│ Jetsam 优先级 (部分): │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 优先级 │ 描述 │ 被 kill 顺序 │ │
│ │ ──────────────────────────────────────────────────────────│ │
│ │ 0-10 │ 后台应用 │ 最先被 kill │ │
│ │ 10-50 │ 后台服务、扩展 │ 其次 │ │
│ │ 50-100 │ 前台应用 │ 较晚 │ │
│ │ 100+ │ 系统进程 │ 最后 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 不同设备的内存限制(大约): │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 设备 │ 物理内存 │ App 限制 (约) │ │
│ │ ───────────────────────────────────────────────────────── │ │
│ │ iPhone 15 Pro │ 8GB │ 2-3 GB │ │
│ │ iPhone 15 │ 6GB │ 1.5-2 GB │ │
│ │ iPhone 14 │ 6GB │ 1.5-2 GB │ │
│ │ iPhone SE 3 │ 4GB │ 1-1.5 GB │ │
│ │ iPad Pro M2 │ 8/16GB │ 3-5 GB │ │
│ │ 老设备 (4GB以下) │ < 4GB │ < 1 GB │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
8.2 检测和处理内存警告
objc
@interface MemoryWarningHandler : NSObject
+ (void)setup;
+ (BOOL)isMemoryPressureHigh;
+ (uint64_t)availableMemory;
@end
@implementation MemoryWarningHandler
+ (void)setup {
// 监听内存警告通知
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
[self handleMemoryWarning];
}];
// 使用 dispatch source 监听系统内存压力
dispatch_source_t memoryPressureSource = dispatch_source_create(
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,
0,
DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL,
dispatch_get_main_queue()
);
dispatch_source_set_event_handler(memoryPressureSource, ^{
dispatch_source_memorypressure_flags_t flags =
dispatch_source_get_data(memoryPressureSource);
if (flags & DISPATCH_MEMORYPRESSURE_CRITICAL) {
NSLog(@"🔴 Critical memory pressure!");
[self handleCriticalMemoryPressure];
} else if (flags & DISPATCH_MEMORYPRESSURE_WARN) {
NSLog(@"🟡 Memory pressure warning");
[self handleMemoryWarning];
}
});
dispatch_resume(memoryPressureSource);
}
+ (void)handleMemoryWarning {
NSLog(@"Handling memory warning...");
// 1. 清除图片缓存
[[SDImageCache sharedImageCache] clearMemory];
// 2. 清除 URL 缓存
[[NSURLCache sharedURLCache] removeAllCachedResponses];
// 3. 清除自定义缓存
[[AppCache shared] clearAllCaches];
// 4. 通知各模块释放资源
[[NSNotificationCenter defaultCenter]
postNotificationName:@"AppShouldReleaseMemory" object:nil];
// 5. 记录日志
[self logMemoryStatus];
}
+ (void)handleCriticalMemoryPressure {
// 临界状态,更激进的释放
[self handleMemoryWarning];
// 额外措施
// 1. 释放非必要的 ViewController
// 2. 取消正在进行的网络请求
// 3. 保存关键数据
}
+ (BOOL)isMemoryPressureHigh {
uint64_t available = [self availableMemory];
uint64_t limit = [self memoryLimit];
// 当可用内存低于限制的 20% 时认为压力高
return available < limit * 0.2;
}
+ (uint64_t)availableMemory {
if (@available(iOS 13.0, *)) {
return os_proc_available_memory();
}
// 备用方案
vm_statistics64_data_t stats;
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
if (host_statistics64(mach_host_self(), HOST_VM_INFO64,
(host_info64_t)&stats, &count) == KERN_SUCCESS) {
uint64_t pageSize = vm_page_size;
return stats.free_count * pageSize;
}
return 0;
}
+ (uint64_t)memoryLimit {
// 获取进程的内存限制
// 这个值不是公开 API,需要通过其他方式估算
task_vm_info_data_t info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
if (task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&info, &count) == KERN_SUCCESS) {
return info.limit_bytes_remaining + info.phys_footprint;
}
// 备用:根据设备内存估算
size_t size = sizeof(uint64_t);
uint64_t memsize;
sysctlbyname("hw.memsize", &memsize, &size, NULL, 0);
// App 通常可以使用物理内存的 40-60%
return memsize * 0.5;
}
+ (void)logMemoryStatus {
task_vm_info_data_t info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
if (task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&info, &count) == KERN_SUCCESS) {
NSLog(@"=== Memory Status ===");
NSLog(@"Footprint: %.2f MB", info.phys_footprint / (1024.0 * 1024.0));
NSLog(@"Available: %.2f MB", [self availableMemory] / (1024.0 * 1024.0));
NSLog(@"Limit: %.2f MB", [self memoryLimit] / (1024.0 * 1024.0));
}
}
@end
8.3 OOM 崩溃分析
objc
// OOM 崩溃通常没有堆栈,需要通过其他方式检测
@interface OOMDetector : NSObject
+ (void)setup;
+ (BOOL)didOOMLastLaunch;
@end
@implementation OOMDetector
static NSString *const kLastLaunchInfoKey = @"OOMDetector_LastLaunchInfo";
static NSString *const kCleanExitKey = @"OOMDetector_CleanExit";
+ (void)setup {
// 检查上次是否正常退出
if ([self didOOMLastLaunch]) {
NSLog(@"🔴 Detected OOM from last launch!");
[self reportOOM];
}
// 记录启动信息
[self recordLaunchInfo];
// 标记为非正常退出(如果正常退出会设置为 YES)
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:kCleanExitKey];
// 监听正常退出
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationWillTerminateNotification
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kCleanExitKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}];
}
+ (BOOL)didOOMLastLaunch {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// 如果上次是正常退出,不是 OOM
if ([defaults boolForKey:kCleanExitKey]) {
return NO;
}
// 检查是否有上次启动的信息
NSDictionary *lastLaunchInfo = [defaults objectForKey:kLastLaunchInfoKey];
if (!lastLaunchInfo) {
return NO; // 首次启动
}
// 检查上次的内存状态
// 如果内存使用很高,可能是 OOM
NSNumber *lastFootprint = lastLaunchInfo[@"lastFootprint"];
if (lastFootprint && lastFootprint.doubleValue > 500 * 1024 * 1024) {
// 上次使用超过 500MB,可能是 OOM
return YES;
}
return NO;
}
+ (void)recordLaunchInfo {
// 定期记录内存使用情况
dispatch_source_t timer = dispatch_source_create(
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
);
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, 0),
10 * NSEC_PER_SEC, // 每 10 秒
NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
task_vm_info_data_t info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
if (task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&info, &count) == KERN_SUCCESS) {
NSDictionary *launchInfo = @{
@"lastFootprint": @(info.phys_footprint),
@"timestamp": @([[NSDate date] timeIntervalSince1970])
};
[[NSUserDefaults standardUserDefaults]
setObject:launchInfo forKey:kLastLaunchInfoKey];
}
});
dispatch_resume(timer);
}
+ (void)reportOOM {
// 上报 OOM 事件
NSLog(@"Reporting OOM crash...");
// 收集信息
NSDictionary *lastLaunchInfo = [[NSUserDefaults standardUserDefaults]
objectForKey:kLastLaunchInfoKey];
// 发送到崩溃收集服务
// [CrashReporter reportOOMWithInfo:lastLaunchInfo];
}
@end
九、总结
9.1 核心概念速查
┌─────────────────────────────────────────────────────────────────────┐
│ iOS 内存管理核心概念 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 【内存类型】 │
│ • Dirty Memory: 被修改过的内存,计入 footprint,优化重点 │
│ • Clean Memory: 可重新加载的内存,可随时丢弃,不计入 footprint │
│ • Compressed: 被压缩的脏内存,计入 footprint │
│ • Virtual: 虚拟地址空间,可能远大于物理内存 │
│ • Resident: 实际驻留在物理内存中的页面 │
│ │
│ 【Malloc 层次】 │
│ • Nano Zone: ≤256B,无锁分配,最快 │
│ • Scalable Zone: ≤1MB,Magazine 架构 │
│ • Large: >1MB,直接 vm_allocate │
│ │
│ 【VM 系统】 │
│ • 页大小: 16KB (ARM64) / 4KB │
│ • ASLR: 地址空间随机化 │
│ • COW: 写时复制,优化内存使用 │
│ │
│ 【内存限制】 │
│ • App 限制约为物理内存的 40-60% │
│ • 超限会被 Jetsam kill │
│ • FOOM: 前台 OOM,无崩溃堆栈 │
│ │
│ 【优化策略】 │
│ • 减少 Dirty Memory │
│ • 使用 mmap 代替 malloc 读取文件 │
│ • 使用 NSCache/NSPurgeableData │
│ • 图片下采样加载 │
│ • 及时响应内存警告 │
│ │
└─────────────────────────────────────────────────────────────────────┘
9.2 调试工具速查
| 工具 | 用途 | 使用方式 |
|---|---|---|
vmmap |
查看内存映射 | vmmap <pid> |
heap |
分析堆内存 | heap <pid> |
leaks |
检测泄漏 | leaks <pid> |
footprint |
查看 footprint | footprint <pid> |
| Instruments - Allocations | 追踪分配 | Xcode Profile |
| Instruments - VM Tracker | 分析 VM | Xcode Profile |
| Memory Graph | 对象引用图 | Xcode Debug |
MallocStackLogging |
记录分配栈 | 环境变量 |
9.3 最佳实践
□ 使用 os_proc_available_memory() 检查可用内存
□ 响应 didReceiveMemoryWarning 释放资源
□ 图片使用下采样加载,匹配显示大小
□ 大文件使用 mmap 映射(clean memory)
□ 使用 NSCache 而非 NSDictionary 做缓存
□ 避免一次性加载大量数据
□ 使用 autoreleasepool 及时释放临时对象
□ 监控内存使用,设置告警阈值
□ 进入后台时释放非必要资源
□ 定期分析内存组成,优化 dirty memory