QNetworkAccessManager内存泄漏问题求助(附SSCCE代码)
核心问题分析
你的代码中存在三个关键问题,导致了缓慢的内存泄漏:
未及时取消超时定时器
每个请求都会创建一个500ms的单Shot定时器,即使请求在超时前正常完成,该定时器仍会运行至触发。定时器对象本身占用内存,且其捕获的reply指针会延迟相关资源的释放。超时处理未中止网络请求
触发超时后直接调用reply->deleteLater(),未先调用reply->abort()中止网络请求。此时QNetworkAccessManager可能仍在后台处理该请求,导致socket、缓冲区等网络资源无法被正确回收。悬空指针引发的集合操作风险
当reply被标记为延迟销毁后,activeReplies集合中移除了该指针,但超时定时器的lambda仍持有悬空指针。此时检查activeReplies.contains(reply)属于未定义行为,可能破坏QSet内部结构,进而引发内存泄漏。
修复后的代码示例
#include <QByteArray> #include <QCoreApplication> #include <QDateTime> #include <QDebug> #include <QFile> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QTextStream> #include <QTimer> #include <QUrl> QMap<QString, QString> status() { QFile file("/proc/self/status"); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Failed to open /proc/self/status"; return {}; } auto ba = file.readAll(); auto list = QString::fromUtf8(ba).split("\n"); QMap<QString, QString> retval; for (auto const &line : list) { auto keyValue = line.split(":\t"); if (keyValue.size() != 2) continue; retval[keyValue.first()] = keyValue.last().simplified(); } return retval; } void reportMemory() { auto timestamp = QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); auto memoryUsage = status()["RssAnon"]; qDebug() << "*****" << timestamp << memoryUsage << "*****"; QFile file("memory_log.csv"); if (!file.open(QIODevice::WriteOnly | QIODevice::Append)) { qWarning() << "Failed to open memory_log.csv"; return; } QTextStream stream(&file); stream << timestamp << ";" << memoryUsage << "\n"; } QNetworkAccessManager qnam; QMap<QNetworkReply*, QTimer*> activeRequests; void onRequestFinished(QNetworkReply *reply) { if (!activeRequests.contains(reply)) return; // 取消并销毁对应的超时定时器 auto timer = activeRequests.take(reply); timer->stop(); timer->deleteLater(); reply->deleteLater(); auto data = reply->readAll(); qDebug() << "Reply size is" << data.size(); } void onRequestTimer() { QUrl url("http://172.20.0.10:12229"); auto reply = qnam.get({QNetworkRequest(url)}); auto timer = new QTimer(); timer->setSingleShot(true); // 关联超时处理:先中止请求,再销毁资源 QObject::connect(timer, &QTimer::timeout, [reply, timer]() { if (!activeRequests.contains(reply)) return; activeRequests.remove(reply); reply->abort(); reply->deleteLater(); timer->deleteLater(); qDebug() << "Reply timeout"; }); activeRequests.insert(reply, timer); timer->start(500); } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // Memory reporting QTimer memoryReportTimer; QObject::connect(&memoryReportTimer, &QTimer::timeout, &reportMemory); memoryReportTimer.start(1000 * 60); reportMemory(); // Network request processing QObject::connect(&qnam, &QNetworkAccessManager::finished, &onRequestFinished); QTimer requestTimer; QObject::connect(&requestTimer, &QTimer::timeout, &onRequestTimer); requestTimer.start(1000); return a.exec(); }
关键修复点说明
- 使用
QMap<QNetworkReply*, QTimer*>关联请求与对应的超时定时器,确保请求完成时能精准取消并销毁定时器。 - 超时触发时先调用
reply->abort()中止网络请求,确保QNetworkAccessManager回收相关网络资源。 - 完善文件操作的错误检查,避免因文件打开失败导致的隐性问题。
- 移除
QScopeGuard(此处无需额外作用域保护,直接显式销毁对象更清晰)。
有效的内存泄漏排查方法
Qt Creator内存分析器
使用Qt Creator内置的Memory Analyzer(基于Valgrind),它会过滤Qt框架的误报,专注于你的代码产生的内存泄漏。Valgrind Massif工具
相较于memcheck,Massif专注于堆内存分配的分析,能生成内存增长的详细快照,适合排查缓慢的内存泄漏。执行命令:valgrind --tool=massif ./your_app ms_print massif.out.<pid>自定义对象跟踪
在代码中添加计数器,记录QNetworkReply和QTimer的创建与销毁次数,打印计数变化,确认是否存在对象未被销毁的情况。QObject树 Dump
定期调用QCoreApplication::instance()->dumpObjectTree(),输出所有存活的QObject对象,定位未被销毁的定时器或网络请求对象。内存映射对比
使用Linux的pmap工具定期获取进程的内存映射,对比不同时间点的内存区域变化,定位持续增长的内存块。
内容的提问来源于stack exchange,提问作者Kirill Didkovsky

