1. Valgrind分析内存泄露的痛点
Valgrind 是一款在 Linux 系统下功能强大的内存调试和性能分析工具。虽然 Valgrind 可以检测到许多常见的内存泄露问题,但它并不能检测到所有类型的内存泄露。例如,对于一些通过复杂的指针操作或者在特定的并发场景下产生的内存泄露,Valgrind 可能无法准确识别。
在某些情况下,Valgrind 可能会产生误报,将一些正常的内存使用情况报告为内存泄露。这可能是由于程序中使用了一些特殊的内存管理方式或者与特定的库函数交互时,Valgrind 无法正确理解其行为所致。另一方面,也可能存在漏报的情况,即实际存在内存泄露,但 Valgrind 没有检测到。
对于大型复杂的项目,Valgrind 生成的报告可能会非常庞大和复杂,包含大量的信息,这使得开发人员很难从中快速定位到真正的内存泄露问题所在。分析这些报告需要花费大量的时间和精力,并且需要开发人员对 Valgrind 的输出格式和相关工具链有深入的了解。
所以,我们还需要借助其他的工具来辅助我们进行一些比较复杂场景下的内存泄露的分析。
2. massif-visualizer 介绍
massif-visualizer
是一款用于可视化 Valgrind 的 Massif 工具输出结果的图形化工具。Massif 是 Valgrind 套件中的一个内存分析工具,它能够详细记录程序在运行过程中的内存使用情况,不过其输出的是文本格式的数据,解读起来具有一定难度。而massif-visualizer
可以将这些数据转化为直观的图形,方便开发者分析和理解程序的内存使用状况
3. 工具的安装
这里的安装和使用以Ubuntu为例
(1)Valgrind安装
sudo apt install valgrind
valgrind --version // 验证安装是否成功
(2)massif-visualizer安装
sudo apt install massif-visualizer
massif-visualizer --version // 验证安装是否成功
4. 示例代码
test_leak.cpp
我们启动三个线程,在其中一个线程中我们不断插入数据,从而模拟我们的业务场景
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
void ThreadFunction1() {
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "threadFunction1" << std::endl;
}
}
void ThreadFunction2() {
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void ThreadFunction3() {
std::vector<int> vecNode;
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
int a = 1;
vecNode.emplace_back(a);
}
}
int main() {
std::thread t1(ThreadFunction1);
std::thread t2(ThreadFunction2);
std::this_thread::sleep_for(std::chrono::seconds(10));
std::thread t3(ThreadFunction3);
t1.join();
t2.join();
t3.join();
return 0;
}
5. 运行分析
编译完成后,运行指令前方加上 valgrind --tool=massif 即可
g++ -std=c++11 -O3 -pthread test_leak.cpp -o test
// 运行
valgrind --tool=massif ./test
当我们压测时长达到要求时,可以Ctrl + C 结束程序,会在同级目录下生成一个 massif.out.{进程id号} 的文件,我们拿到这个文件后就可以使用massif-visualizer进行可视化分析了
massif-visualizer massif.out.6116
我们发现程序运行一段时间后内存进行了疯狂的增长(注意,右侧粉红色内存占用下降会是因为Ctrl + C 掉了程序),而且可以看到占用较高的ThreadFunction3 线程函数
6. 解决后验证
我们根据堆栈信息找到代码中具体位置后,进行修改, 消费掉这个数据后再进行一段时间的压测
void ThreadFunction3() {
std::vector<int> vecNode;
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
int a = 1;
vecNode.emplace_back(a);
// Todo: 模拟处理完了业务后可以删除掉数据释放内存
vecNode.pop_back(a);
}
}
这时候可以发现程序在运行一段时间后就趋于了平稳状态