如何在macOS与Windows系统中检测内存碎片化以排查OOM问题
你遇到的这种OOM找不到泄漏、怀疑内存碎片化的情况太常见了——长期运行的进程很容易把内存拆成一堆小空闲块,总内存够但就是拿不出连续的大块。我分平台给你梳理下具体的检测方法:
macOS 平台的内存碎片化检测
1. 直接用vmmap获取碎片化数据
你提到的vmmap碎片化计算公式完全正确:
(DIRTY+SWAP FRAG SIZE) = DIRTY SIZE - BYTES ALLOCATED
FRAG = (DIRTY+SWAP FRAG SIZE) / DIRTY SIZE × 100.0
其实vmmap本身的输出就包含你要的DIRTY SIZE和BYTES ALLOCATED,不用额外找工具:
- 运行
vmmap <你的进程PID>,定位到进程的malloc相关内存段(比如MALLOC_LARGE、MALLOC_SMALL、MALLOC_TINY); - 每个段下的
Dirty行对应DIRTY SIZE(例:Dirty: 123456 KB); - 同一段的
Allocated行就是BYTES ALLOCATED(例:Allocated: 98765 KB); - 嫌手动计算麻烦?直接看输出里的
FRAG列,它已经帮你算好碎片化百分比了。
另外,vmmap -summary <PID>能快速输出进程整体内存的碎片化概况,不用翻冗长的详细日志。
2. 解决mstats与vmmap数据不匹配的问题
mstats只跟踪libmalloc分配器管理的内存,而vmmap是系统级的全局内存视图,包含了内核分配内存、共享库内存等其他区域,数据不一致是正常的。如果想从代码层面获取和vmmap中malloc段对齐的统计数据,可以用malloc_zone_statistics API,它能直接查询每个malloc zone的已分配、空闲内存细节,和vmmap的malloc段数据完全匹配。
3. 辅助工具:heap命令
运行heap -s <PID>可以查看进程堆的分配统计,比如不同大小内存块的数量——如果小内存块占比极高,且空闲块数量多但单个块极小,这就是典型的碎片化特征。
Windows 平台的内存碎片化检测
Windows的内存模型和macOS差异很大,你用VirtualQuery和GetProcessMemoryInfo的思路是对的,但需要针对性调整分析逻辑:
1. 改造VirtualQuery分析内存区域分布
你当前的vminfo函数只统计了总free、reserved、committed内存,这不足以检测碎片化。要判断碎片化,你需要重点分析空闲内存块的分布:
- 遍历所有
MEM_FREE类型的内存区域,记录每个空闲块的大小; - 计算总空闲内存和最大连续空闲块大小:如果总空闲内存足够,但最大连续块远小于你需要分配的内存尺寸,那就是碎片化导致的OOM;
- 额外统计空闲块数量——数量越多、平均块越小,碎片化越严重。
给你修改后的代码示例:
static void vminfo_fragmentation(unsigned long *total_free, unsigned long *max_free_block, unsigned int *free_block_count) { MEMORY_BASIC_INFORMATION memory_info; memory_info.BaseAddress = 0; *total_free = 0; *max_free_block = 0; *free_block_count = 0; while (VirtualQuery(memory_info.BaseAddress, &memory_info, sizeof(memory_info))) { if (memory_info.State == MEM_FREE) { *total_free += memory_info.RegionSize; *free_block_count += 1; if (memory_info.RegionSize > *max_free_block) { *max_free_block = memory_info.RegionSize; } } memory_info.BaseAddress = (char *)memory_info.BaseAddress + memory_info.RegionSize; } }
2. 用HeapWalk分析堆级碎片化
如果你的OOM来自堆分配(比如malloc/new),用HeapWalk分析进程的堆会更精准:
- 先用
GetProcessHeaps获取进程的所有堆句柄; - 用
HeapWalk遍历每个堆的内存块,区分已分配块和空闲块; - 统计空闲块的大小分布、最大空闲块,以及碎片化率(公式参考:
(总空闲堆内存 - 最大空闲堆块) / 总空闲堆内存 * 100%)。
这种方法能直接定位到堆内部的碎片化情况,比系统级的VirtualQuery更贴近你的业务分配逻辑。
3. GetProcessMemoryInfo的辅助作用
PROCESS_MEMORY_COUNTERS_EX里的指标更多是整体内存使用概况,比如:
PrivateUsage:进程私有提交的内存总量;WorkingSetSize:当前驻留在物理内存中的内存量;
这些指标不能直接检测碎片化,但可以结合VirtualQuery的结果佐证——比如PrivateUsage很高,但总空闲内存也不低,就更能确认是碎片化导致的分配失败。
总结
- macOS:优先用
vmmap <PID>查看FRAG列,或手动提取DIRTY SIZE和BYTES ALLOCATED计算;代码层面用malloc_zone_statistics获取精准的malloc分配数据; - Windows:用
VirtualQuery分析系统级空闲内存块分布,或用HeapWalk分析堆内部的碎片化;结合GetProcessMemoryInfo的整体指标辅助验证。
内容的提问来源于stack exchange,提问作者Murat Şeker

