最近写了一个c++项目来学习,发现客户端在多次下载文件后内存只增不减,因此想利用
valgrind
来对我写的客户端进行内存泄漏检测
检测命令 && 工具
检测命令
我这次主要是使用 valgrind 中的 massif 来进行内存泄漏检测
valgrind --tool=massif --time-unit=ms --detailed-freq=1 --massif-out-file=./valgrindCheck/massif.out.client ./build/TRANFILER_CLIENT
# --time-unit=<i|ms|B> [default: i] 可以说是记录的颗粒度,如果程序运行很快就用B
# The time unit used for the profiling. There are three possibilities: instructions executed (i), which is good for most cases; real (wallclock) time (ms, i.e. milliseconds), which is sometimes useful; and bytes allocated/deallocated on the heap and/or stack (B), which is useful for very short-run programs, and for testing purposes, because it is the most reproducible across different machines.
#--detailed-freq=<n> [default: 10] 每多少次间隔记录一次详细信息
# Frequency of detailed snapshots. With --detailed-freq=1, every snapshot is detailed.
查看工具
生成出来的massif.out.client
文件,使用 massif-visualizer 来查看
测试过程
客户端下载一个文件,然后再下载另一个,以此反复几次
检测分析
首先按照正常结构来说,客户端内存使用会在下载文件的时候上升(保存下载记录),在下载完成后内存使用会下降
分析结果
通过上图可以知道正常的内存使用都是在使用完后会被销毁的,即图中的有上下坡线的内存,而存在内存泄漏的内存的内存就只有不断的上升
打开一个进行详情的分析
- 程序中用的是
protobuf
进行消息序列化的,因此带有google::protobuf
的内存信息,可以通过在下载结束的析构函数里面添加 google::protobuf::ShutdownProtobufLibrary() 去解决 在本项目中只要主线程有下载或者发送数据就会用到protobuf所以就不做处理了 - 只有
ack::AckSet::setCbByAck
是在程序中与 protobuf 不太相关的 - 虽然
downfile::interruption
跟 protobuf 相关但是这个是本次下载数据的临时保存,因此在下载完也应该被释放
问题修复
ack::AckSet::setCbByAck
查看对应的代码
//调用段代码
_ackSetPtr->setCbByAck(ack, std::bind(&DownloaderEvents::timerExce, this, ack, resMsg));
//对应方法
void AckSet::setCbByAck(u_long& ack, Cb cb) {
std::lock_guard<std::mutex> lock_guard(_ackMsgMapLock);
auto it = _ackMsgMap.find(ack);
if (it == _ackMsgMap.end()) {
return;
}
it->second = _timerPtr->runEvery(config::ClientConfig::getInstance().getPacketsTimerOut(), cb);
}
//通过查看方法发现调用了添加定时器的代码
uint Timer::runEvery(u_long timerout, TimerCb cb) {
TimerEvenSharedPtr even = std::make_shared<timerEven>();
even->timeout = timerout;
even->interval = true;
even->cd = cb;
runAfter(even);
uint index = _currTimerIndex++;
_allTimerEven[index] = even;
return index;
}
其中 std::unordered_map<unsigned int, std::shared_ptr<timer::timerEven>
就是 _allTimerEven
一个用于保存当前定时器中所有定时任务的成员对象,但是我在一定条件下会取消该定时任务,我查找到了对应的代码
void cancelTimerEven(uint index) { _allTimerEven[index].reset(); };
//通过释放shared_ptr 可以导致在获取超时even(weak_ptr)时 无法通过 lock() 获取其对象 从而得出该定时事件已经被取消
是的,在这个方法里面只有释放了该定时器对应的 TimerEvenSharedPtr even
的指针,没有释放对应 index
的下标的空间,修改后的代码
void cancelTimerEven(uint index) {
_allTimerEven[index].reset();
_allTimerEven.erase(index);
};
downfile::interruption
这个是用于保存 protobuf
的数据,其中 SingleBlockInfo
是一个 repeated
类型的数据,因此可以用只能指针的方式,在下载结束时条用其clear()
和reset()
方法来释放内存
//下载结束要判断是否下载完成,未完成则保存中断数据到文件,完成的就刷新出正常的文件名出来
void Downloader::flushAllInterruptionData() {
for (auto &it : _downfileInterruptionInfos) {
if (!it.second->isfinish()) {
flushInterruptionData(it.first);
} else {
delFlushInterruptionFile(it.first);
}
it.second->clear_info();
it.second.reset();
}
_downfileInterruptionInfos.clear();
}
重新测试
经过上述的代码修改后,重新运行了一下程序,得到了以下结果
当观察内存详细时发现 ack::AckSet::setCbByAck
已经不见了,也就是说内存被正常释放了
其中的downfile::interruption
在一次下载完成后也出现了明显的内存使用下降
内存泄漏分析至此就完成了 ✌✌✌