可用 sigusr2 在运行中手动触发 massif 内存快照,需启动时指定 --massif-out-file=massif.out.%p,通过 ps 找到被 valgrind 包裹的子进程 pid 后执行 kill -sigusr2 ,快照按时间顺序追加至同一文件,用 ms_print --detailed=yes 解析可查看全部快照。

怎么用 SIGUSR2 在运行中手动打内存快照
Massif 默认只在程序退出时写一次报告,没法看到中间关键阶段的内存状态。想盯住某个函数执行前后、某次请求处理中、或某段循环迭代里的内存波动,就得主动触发快照——靠 SIGUSR2 信号。
实操要点:
- 启动时必须加
--massif-out-file=massif.out.%p,否则多快照会覆盖(%p确保文件名带 PID) - 程序跑起来后,用
ps aux | grep your_app找到 valgrind 包裹后的子进程 PID(不是最外层 valgrind 的 PID) - 发信号:
kill -SIGUSR2 <pid></pid>,Massif 收到后立刻 dump 当前堆/栈快照到同一文件,不中断程序 - 可重复发多次,快照按时间顺序追加,
ms_print解析时会自动分节显示n=0、n=1…
如何让 Massif 只记录你关心的时间段
Massif 本身没有“start/stop recording”开关,但可以通过参数缩小干扰、聚焦关键路径:
常见做法:
- 用
--time-unit=B:以累计分配字节数为时间轴,比默认的指令数i更贴近内存行为,尤其适合长周期服务里定位某次大 alloc - 加
--threshold=0.01(或更低):过滤掉占比小于 1% 的调用栈,避免被 stdio、libc 初始化等噪音淹没 - 如果关键逻辑在子进程里(比如 fork 后 worker 处理请求),务必加
--trace-children=yes,否则只录到父进程 - 避免
--pages-as-heap=yes,它会让 Massif 把所有 mmap 内存都当堆统计,报告体积暴增且难解读;真要查 mmap 行为,该用memcheck或perf
为什么 ms_print 报告里找不到你刚打的快照
不是没生成,是没读对位置。Massif 输出文件是二进制格式,ms_print 解析时默认只显示“峰值快照”和 top 调用栈,中间手动打的快照容易被忽略。
排查步骤:
- 先用
hexdump -C massif.out.* | head -20确认文件非空,且开头有VALGRIND_MASSIF字样 - 用
ms_print --detailed=yes massif.out.*强制展开所有快照细节(含n=0到n=N) - 检查报告底部的
Detail区块是否包含你预期的函数名和行号;如果没有,大概率是编译没加-g,或优化导致内联(-O2以上基本不可信) - 若仍无调用栈,试试
massif-visualizer massif.out.*,图形界面能直接拖动时间轴看每个快照的堆分布
多线程下怎么确保 worker 线程的分配被采到
Massif 默认只跟踪主线程的 malloc/new,worker 线程里分配的内存不会出现在调用栈里——这是最容易被忽略的盲区。
两个选择:
- 加
--pages-as-heap=yes:强制把所有匿名页都当堆处理,能捕获所有线程的分配,但性能开销增加 10 倍以上,仅限短时诊断 - 更稳妥的做法:在 worker 线程入口处加
pthread_setname_np(pthread_self(), "worker"),再配合--stacks=yes,至少能从栈帧名反推线程上下文(虽然还是看不到具体 new 调用点) - 终极方案:改代码,在关键 worker 分配路径前后手动调用
malloc_stats()或记录mallinfo(),和 Massif 快照时间戳对齐人工比对
真正难的不是打快照,而是确认那个“关键阶段”在程序里到底对应哪几行——Massif 不会告诉你逻辑边界,得靠你自己在代码里埋点、记日志、再和快照时间戳对齐。否则快照再多,也只是一堆 bytes 和 ???。











