You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

QNetworkAccessManager内存泄漏问题求助(附SSCCE代码)

Qt网络请求内存泄漏排查与修复

核心问题分析

你的代码中存在三个关键问题,导致了缓慢的内存泄漏:

  1. 未及时取消超时定时器
    每个请求都会创建一个500ms的单Shot定时器,即使请求在超时前正常完成,该定时器仍会运行至触发。定时器对象本身占用内存,且其捕获的reply指针会延迟相关资源的释放。

  2. 超时处理未中止网络请求
    触发超时后直接调用reply->deleteLater(),未先调用reply->abort()中止网络请求。此时QNetworkAccessManager可能仍在后台处理该请求,导致socket、缓冲区等网络资源无法被正确回收。

  3. 悬空指针引发的集合操作风险
    当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(此处无需额外作用域保护,直接显式销毁对象更清晰)。

有效的内存泄漏排查方法

  1. Qt Creator内存分析器
    使用Qt Creator内置的Memory Analyzer(基于Valgrind),它会过滤Qt框架的误报,专注于你的代码产生的内存泄漏。

  2. Valgrind Massif工具
    相较于memcheck,Massif专注于堆内存分配的分析,能生成内存增长的详细快照,适合排查缓慢的内存泄漏。执行命令:

    valgrind --tool=massif ./your_app
    ms_print massif.out.<pid>
    
  3. 自定义对象跟踪
    在代码中添加计数器,记录QNetworkReply和QTimer的创建与销毁次数,打印计数变化,确认是否存在对象未被销毁的情况。

  4. QObject树 Dump
    定期调用QCoreApplication::instance()->dumpObjectTree(),输出所有存活的QObject对象,定位未被销毁的定时器或网络请求对象。

  5. 内存映射对比
    使用Linux的pmap工具定期获取进程的内存映射,对比不同时间点的内存区域变化,定位持续增长的内存块。

内容的提问来源于stack exchange,提问作者Kirill Didkovsky

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 04:20:54