massif.out文件需用ms_print解析才可读;其报告分三块:顶部曲线摘要标峰值时刻,中间节点列表按分配量倒序定位泄漏源,底部Detail段展开快照调用栈确认路径。

massif.out 文件结构怎么看
massif.out 不是人类可读的纯文本报告,而是 Massif 生成的二进制/类文本快照数据文件,直接 cat 或 vim 打开会看到大量不可读字符或乱码行(尤其含非 ASCII 时间戳或堆栈符号时)。它必须用 ms_print 解析才能得到结构化输出。
真正能看懂的,是 ms_print massif.out.12345 > report.txt 之后的文本报告——这个报告分三块,每块都有明确用途:
-
顶部曲线摘要:显示
time(B)(按字节分配量计时)与total(B)的关系图,标出峰值时刻(如@123456),这是你第一眼该盯住的位置 -
中间“->1”节点列表:按累计分配量倒序排列,每行含
percent、bytes和完整调用栈(含file.cpp:123),它是定位“谁在疯狂 malloc”的核心依据 -
底部“Detail”段:展开某个快照编号(如
n=7)的完整栈帧,确认该次分配是否来自你预期的函数路径,比如std::vector::resize调用了operator new
为什么 ms_print 报告里看不到 new 分配
Massif 默认只跟踪 malloc/realloc/calloc/free 系列调用。C++ 的 new 表达式若未重载全局 operator new,底层仍走 malloc,所以能捕获;但一旦你写了自定义 operator new 且没显式调用 malloc(比如用了 mmap 或内存池),Massif 就完全漏掉这部分堆分配。
验证方法很简单:在代码里加一句 malloc(1024);,再跑一遍 ms_print,如果这行出现在报告里,说明 Massif 工作正常;如果 new int[1024] 没出现,而 malloc 出现了,基本就是 operator new 被重载且绕过了标准分配器。
- 编译时务必带
-g,否则调用栈里只有地址,没有file.cpp:line - 避免使用
-O2以上优化,内联可能让栈帧丢失关键中间函数 - 若用
std::string或std::vector,它们内部的扩容行为会体现在operator new或malloc调用中,但前提是没被编译器优化掉
stacks(B) 列为 0 是不是代表没分析栈
是,默认情况下 --stacks=no,Massif 完全不采集栈内存数据,stacks(B) 恒为 0。这不是 bug,是设计如此——开启栈分析会让性能下降数倍,且多数内存问题集中在堆上。
要启用栈分析,必须显式加参数:--stacks=yes。注意两点:
- 启用后
ms_print输出中才会出现非零的stacks(B)值,以及栈帧中stack标记的调用项 - Massif 假设主栈初始大小为 0,所以它显示的是“程序运行期间增长的栈用量”,不是 OS 分配的整个栈空间(通常 8MB)
- 线程栈也会被统计,但前提是线程是用
pthread_create启动的,且 Massif 能 hook 到其栈创建点
如何快速定位某次快照对应的代码位置
Massif 报告里的快照编号 n=xx 对应一个时间点,但不直接对应代码行号。你需要结合 @123456(峰值标记)和 “->1” 节点中的调用栈来反推。
典型操作流程:
- 先扫一眼顶部,找到峰值标记,比如
@123456 - 往下翻到 “->1” 列表,找
percent最高那一行,看它的调用栈最后一行(最深调用),例如my_module.cpp:89 - 如果该行是容器 push_back 或 resize,继续往上调用栈看——真正触发分配的往往是它的上层逻辑,比如循环体或事件回调
- 若想确认该快照是否真发生在某段逻辑内,可在代码中插入
raise(SIGUSR2);(需提前用--time-unit=B启动),然后比对快照n编号与你预期的位置是否接近
最易忽略的一点:Massif 的快照是采样点,不是精确断点。两次 SIGUSR2 之间可能跳过若干小分配,所以别指望靠它抓单次 new,而要看趋势和累计占比。


















