剖析 stl + glibc “内存泄漏” 原因

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

最近项目增加了一个模块,在 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

图片来源:《深入理解计算机系统》

# 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 */
...
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值