0 概括
本文用于记录 x86-Linux 应用程序发生持续性内存泄漏问题时的定位方法。主要介绍valgrind工具的应用。
1 原理
对于内存泄漏问题的定位,一种朴素的想法就是对内存申请点进行监控。对于一个内存申请调用点(例如c/c++中的malloc函数),记录下该调用点被触发时的调用栈 c a l l s t a c k callstack callstack,以及申请到的内存 m e m o r y memory memory。同一个调用栈可能申请多次内存,我们把申请到的内存都记录下来,于是我们得到了一个统计表,表中元素长这样: ( c a l l s t a c k i , { m e m o r y i 1 , m e m o r y i 2 , . . . } ) (callstack_i,\{memory_{i}^{1},memory_{i}^{2},...\}) (callstacki,{memoryi1,memoryi2,...})。当 m e m o r y i j memory_{i}^{j} memoryij内存被释放(例如c/c++中的free函数),则把统计表中的 m e m o r y i j memory_{i}^{j} memoryij项删除即可。如此一来,当程序结束,依旧持有 m e m o r y memory memory的那些 c a l l s t a c k callstack callstack就可以认为是潜在的内存泄漏点。
上述的朴素想法,ASAN工具已经帮我们实现。本文讨论更特殊的场景,对ASAN工具的使用不再赘述。在程序持续运行,持续内存泄漏的场景中,我们需要对上述朴素想法做一点点改变。在 t i m e s t a m p timestamp timestamp时刻,我们记录一下该时刻的统计表 ( c a l l s t a c k i , { m e m o r y i 1 , m e m o r y i 2 , . . . } ) (callstack_i,\{memory_{i}^{1},memory_{i}^{2},...\}) (callstacki,{memoryi1,memoryi2,...})。随程序的运行,我们便得到了一张随时间变化的统计表 [ t i m e s t a m p , { ( c a l l s t a c k i , { m e m o r y i 1 , m e m o r y i 2 , . . . } } ) ] [timestamp, \{(callstack_i,\{memory_{i}^{1},memory_{i}^{2},...\}\})] [timestamp,{(callstacki,{memoryi1,memoryi2,...}})]。某一时刻的统计表我们称之为一个内存切片。如此一来,当我们观测到一个 c a l l s t a c k i callstack_i callstacki的内存持有量随时间不断增加,则可以识别该点为潜在的内存泄漏点。这种内存切片的想法,valgrind工具已经帮我们实现。
2 valgrind工具的使用
本文不详细介绍valgrind工具,只通过简单案例介绍如何使用valgrind工具形成内存切片图。
x86-Linux服务器环境,先执行以下命令安装valgrind工具。
sudo apt install valgrind
sudo apt install massif-visualizer
再给出一个持续运行持续内存泄漏的简单范例main.cpp。
#include <thread>
#include <chrono>
#include <vector>
#include <string>void sleepMs(int milliseconds) {std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}int main(int /*argc*/, char** /*argv*/) {std::vecotr<std::string> vec;while(true) {// 新增10个数据for (size_t i = 0; i < 10U; i++) {vec.push_back(std::string(1000000, 'A'));sleepMs(100);}// 释放9个数据for (size_t j = 0; j < 9U; j++) {vec.pop_back();sleepMs(100);}}return 0;
}
对应的编译命令为g++ -g -o exe main.cpp
。
接下来使用valgrind运行这个程序,生成内存切片。对应的命令为
valgrind --tool=massif --time-unit=B ./exe
运行一段时间后,ctrl+c结束程序,就生成了对应的内存切片文件massif.out.<pid>。使用可视化工具打开这个内存切片文件,效果如下。
可以看到调用点持有的内存量随时间的变化,还可以看到不同时刻,各调用栈持有的内存量。至此可以协助我们分析持续性的内存泄漏。
3 结论
本文记录持续内存泄漏问题的一种定位方法,记录valgrind工具的使用。但是valgrind工具会极大影响程序性能。对商用程序,如果设计得不好,性能受影响的情况下有可能功能都不正常。所以本文方法只是一种参考,具体情况还需要具体分析。