堆上分配对象删除后内存未释放,求排查思路
每当有新客户端连接时,会在.message回调内的堆上创建一个新的std::string实例,并将客户端发送的message对象值赋值给它。
我观察到内存(RSS项)随新客户端连接持续增长(例如从约9000字节增长到9004字节、9008字节,每两三个新客户端连接就增长一次),但当客户端正常退出触发.close回调并删除该对象后,内存并未下降。客户端侧通过Qt C++的QWebSocket调用clientSocket.close(),服务端会触发.close回调。
测试时每个客户端发送1024字节数据,我在Debian Linux上使用命令watch -n0.2 'pmap -x process-id | tail -n1'实时监控内存使用情况。
我是该库的新手,肯定哪里操作有误。
用户数据结构与回调代码
PerSocketData定义
struct PerSocketData { std::string* someString {nullptr}; };
.message回调
.message = [&](uWS::WebSocket<false, true, PerSocketData> *ws, std::string_view message, uWS::OpCode opCode) { PerSocketData* ud = ws->getUserData(); ud->someString = new std::string(message); }
.close回调
.close = [](uWS::WebSocket<false, true, PerSocketData> *ws, int /*code*/, std::string_view message) { PerSocketData* ud = ws->getUserData(); ud->someString->clear(); //因delete无效尝试此操作 ud->someString->shrink_to_fit(); //因delete无效尝试此操作 delete ud->someString; ud->someString = nullptr; }
问题分析与解决
RSS未下降的核心原因
Linux系统的内存分配器(如glibc的ptmalloc)不会在内存释放后立刻将内存归还操作系统,而是会把空闲内存保留在进程的内存池中,供后续分配请求复用。你看到的RSS增长是进程实际占用的物理内存,释放后的内存会被分配器缓存,所以不会立即下降,这是正常的系统行为,并非内存泄漏。代码中的内存泄漏隐患
当前代码存在一个明显问题:如果同一个客户端多次发送消息,.message回调会直接给ud->someString赋值新的堆内存,之前分配的std::string实例会被覆盖,导致这部分内存永远无法释放,形成真正的内存泄漏。修正后的代码
.message回调中先检查并释放已存在的内存,再创建新实例:
.message = [&](uWS::WebSocket<false, true, PerSocketData> *ws, std::string_view message, uWS::OpCode opCode) { PerSocketData* ud = ws->getUserData(); // 先释放已存在的内存,避免泄漏 if (ud->someString) { delete ud->someString; ud->someString = nullptr; } ud->someString = new std::string(message); }
.close回调无需额外调用clear()和shrink_to_fit(),直接安全释放即可:
.close = [](uWS::WebSocket<false, true, PerSocketData> *ws, int /*code*/, std::string_view message) { PerSocketData* ud = ws->getUserData(); if (ud->someString) { delete ud->someString; ud->someString = nullptr; } }
- 准确验证内存泄漏
不要仅依赖RSS判断,使用专业工具检测:比如valgrind --leak-check=full ./your-program,或者gperftools的堆分析器,这些工具能区分内存分配器缓存和真正的内存泄漏。
内容的提问来源于stack exchange,提问作者falero80s

