最近项目增加了一个模块,在 Centos 系统压测,进程一直不释放内存。因为新增代码量不多,经过排查,发现 stl + glibc 这个经典组合竟然有问题,见鬼了!
通过调试(Centos 调试 glibc 视频)和查阅 glibc 源码,好不容易才搞明白它 “泄漏” 的原因。
问题在于:ptmalloc2 内存池的 fast bins 快速缓存和 top chunk 内存返还系统的特点导致。
🔥 文章来源:剖析 stl + glibc “内存泄漏” 原因
1. 现象
上测试源码看看:

/* g++ -g -std='c++11' example_pressure.cpp -o ep111 && ./ep111 10000 */
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <list>
int main(int argc, char** argv) {
if (argc != 2) {
printf("./proc [count]\n");
return -1;
}
int cnt = atoi(argv[1]);
std::list<char*> free_list;
for (int i = 0; i < cnt; i++) {
char* m = new char[1024 * 64];
memset(m, 'a', 1024 * 64);
free_list.push_back(m);
}
for (auto& v : free_list) {
delete[] v;
}
for (;;) {
sleep(1);
}
return 0;
}
2. 分析
2.1. valgrind
用 valgrind 检查出来的结果,没有释放的部分应该是 free_list 没有调用 clear 导致,但显然不符合预期。
[root:.../coroutine/test_libco/libco]# valgrind --leak-check=full --show-leak-kinds=all ./ep111 20000
==20802== Memcheck, a memory error detector
==20802== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==20802== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==20802== Command: ./ep111 20000
...
==20802==
==20802== HEAP SUMMARY:
==20802== in use at exit: 480,000 bytes in 20,000 blocks
==20802== total heap usage: 40,000 allocs, 20,000 frees, 1,311,200,000 bytes allocated
==20802==
==20802== 480,000 bytes in 20,000 blocks are still reachable in loss record 1 of 1
==20802== at 0x4C2A593: operator new(unsigned long) (vg_replace_malloc.c:344)
==20802== by 0x401186: __gnu_cxx::new_allocator<std::_List_node<char*> >::allocate(unsigned long, void const*) (new_allocator.h:104)
==20802== by 0x4010E5: std::_List_base<char*, std::allocator<char*> >::_M_get_node() (stl_list.h:334)
==20802== by 0x401016: std::_List_node<char*>* std::list<char*, std::allocator<char*> >::_M_create_node<char* const&>(char* const&) (stl_list.h:502)
==20802== by 0x400F21: void std::list<char*, std::allocator<char*> >::_M_insert<char* const&>(std::_List_iterator<char*>, char* const&) (stl_list.h:1561)
==20802== by 0x400D93: std::list<char*, std::allocator<char*> >::push_back(char* const&) (stl_list.h:1016)
==20802== by 0x400BD8: main (example_pressure.cpp:21)
==20802==
==20802== LEAK SUMMARY:
==20802== definitely lost: 0 bytes in 0 blocks
==20802== indirectly lost: 0 bytes in 0 blocks
==20802== possibly lost: 0 bytes in 0 blocks
==20802== still reachable: 480,000 bytes in 20,000 blocks
==20802== suppressed: 0 bytes in 0 blocks
2.2. pmap
用 pmap 命令查看进程,发现 640600K 这一块 [ anon ] 内存很大,应该是这个地方“泄漏”了。

图片来源:《深入理解计算机系统》
# pmap -p 3321
3321: ./ep111 10000
0000000000400000 8K r-x-- ep111
0000000000601000 4K r---- ep111
0000000000602000 4K rw--- ep111
# 640600K 这里数值很大,应该是泄漏的地方了。
000000000180d000 640600K rw--- [ anon ]
00007ff2a8ce5000 1804K r-x-- libc-2.17.so
00007ff2a8ea8000 2048K ----- libc-2.17.so
00007ff2a90a8000 16K r---- libc-2.17.so
00007ff2a90ac000 8K rw--- libc-2.17.so
00007ff2a90ae000 20K rw--- [ anon ]
00007ff2a90b3000 84K r-x-- libgcc_s-4.8.5-20150702.so.1
00007ff2a90c8000 2044K ----- libgcc_s-4.8.5-20150702.so.1
00007ff2a92c7000 4K r---- libgcc_s-4.8.5-20150702.so.1
00007ff2a92c8000 4K rw--- libgcc_s-4.8.5-20150702.so.1
00007ff2a92c9000 1028K r-x-- libm-2.17.so
00007ff2a93ca000 2044K ----- libm-2.17.so
00007ff2a95c9000 4K r---- libm-2.17.so
00007ff2a95ca000 4K rw--- libm-2.17.so
00007ff2a95cb000 932K r-x-- libstdc++.so.6.0.19
00007ff2a96b4000 2044K ----- libstdc++.so.6.0.19
00007ff2a98b3000 32K r---- libstdc++.so.6.0.19
00007ff2a98bb000 8K rw--- libstdc++.so.6.0.19
00007ff2a98bd000 84K rw--- [ anon ]
00007ff2a98d2000 136K r-x-- ld-2.17.so
00007ff2a9ae0000 20K rw--- [ anon ]
00007ff2a9af2000 4K rw--- [ anon ]
00007ff2a9af3000 4K r---- ld-2.17.so
00007ff2a9af4000 4K rw--- ld-2.17.so
00007ff2a9af5000 4K rw--- [ anon ]
00007ffd62611000 132K rw--- [ stack ]
00007ffd62799000 8K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
total 653144K
2.3. malloc 内存信息
通过查看 malloc 分配的内存信息,发现 std::list::clear 后,程序已经全部释放内存。
但是这些已经释放掉的内存是否全部交还给系统了呢?
没有!glibc 缓存起来了。system bytes = 655974400 刚好与 pmap 查到的堆分配内存大小一致。
- malloc_stats 打印 glibc 分配内存信息。
/* example_pressure.cpp */
...

本文详细分析了C++中使用STL与glibc内存管理时遇到的内存泄漏问题,主要原因是ptmalloc2内存池的fastbins缓存和topchunk内存返还机制。通过valgrind、pmap等工具,展示了内存分配和释放的过程,并解释了为何即使释放内存后,glibc可能不会立即归还给系统。文章通过malloc和free的源码分析,揭示了内存回收的条件和malloc_trim的作用。最后,提出了避免内存泄漏和优化内存管理的建议。
最低0.47元/天 解锁文章
1345

被折叠的 条评论
为什么被折叠?



