由STL map调用clear后,内存不返还给操作系统的问题出发,探讨glibc malloc/free行为

本文通过实验分析了在C++中使用STL map调用clear()和析构后内存未返还给操作系统的现象,并探讨了glibc的malloc/free内存管理机制。实验表明,free()并不会立即返还内存,而是通过bin管理、fast bins、top chunk等方式进行内存缓存。此外,文章介绍了ptmalloc2的工作原理,包括main arena、non main arena、chunk组织、内存分配与释放流程。最后,提出了基于mmap自定义内存管理方案,以解决内存返还问题。
摘要由CSDN通过智能技术生成

本博客所有的代码在github中。

1. 问题

我们的程序有几十个线程,每个线程拥有一个std::map,每个线程都要向自己的std::map中插入大量的数据,但每个数据只有几十字节;当使用完std::map,调用map.clear(),删除map里的所有元素,发现std::map所占内存没有返还给操作系统;甚至std::map析构后,内存仍然没有返还给操作系统(map析构不返还内存,不一定100%重现)。

了解了glibc malloc/free原理后,我设计了几个实验,目的是辅助理解。所有测试结果基于Red Hat Enterprise 7.0,glibc版本2.17。

1.1 实验--1

编译需要提前安装openssl, openssl-devel库rpm包。

编译:

[root@mydev-rosvile-redhat 0001]# sh build.sh
Complile mytest success

运行:

[root@mydev-rosvile-redhat 0001]# ./mytest map
----------------------------------------------------------------------------------------------
test_map() 1

At the beginning, map.size=0
Output of 'top':
26503 root      20   0   22900   1528   1172 S   0.0  0.0   0:00.00 mytest
----------------------------------------------------------------------------------------------
test_map() 2

Insert all FPs into std::map, map.size=500000, cost time = 1 seconds
Output of 'top':
26503 root      20   0   54052  32716   1280 S   0.0  0.4   0:00.80 mytest
-------------------------------------------------------------------------------------------------
test_map() 3

Lookup all FPs from std::map, map.size=500000, cost time = 1 seconds
-----------------------------------------------------------------------------------------------
test_map() 4

Delete all FPs from std::map, map.size=0, cost time = 0 seconds
Sleep 15 seconds, Output of 'top':
26503 root      20   0   54052  32928   1320 S   0.0  0.4   0:01.68 mytest
----------------------------------------------------------------------------------------------
test_map() 5

Now the process wil exit and die:
Output of 'top':
26503 root      20   0   54052  32928   1320 S   0.0  0.4   0:01.68 mytest
-----------------------------------------------------------------------------------------------

小提示:'top'输出的第6列表示某程序使用的物理内存大小。

在实验--1里:

1)我们向std::map插入500,000个数据 (插入数据代码) 来模拟我们的业务场景(一个md5值作为key,对应一个uint64_t值作为value)。

2)可以发现map.clear()删除数据后 (删除数据代码), 没有返还内存给操作系统(占用32928 KB)。

3)甚至map析构后 (map析构后), 仍然没有返还内存给操作系统(占用32928 KB)。 map析构不返还内存,不一定100%重现。

思考:

因为即使C++,其new/delete也是基于malloc/free的封装,这难道是glibc malloc/free的特点?实验--2和实验--3将为我们揭晓答案。

1.2 实验--2

运行:

root@mydev-rosvile-redhat 0001]# ./mytest malloc-free
----------------------------------------------------------------------------------------------
test_malloc_free 1

At the beginning:
Output of 'top':
 3417 root      20   0   26692   1532   1168 S   0.0  0.0   0:00.00 mytest
----------------------------------------------------------------------------------------------
test_malloc_free 2

Malloc: number = 500000
Output of 'top':
  3417 root      20   0  534496 513128   1220 S   0.0  6.4   0:00.43 mytest
----------------------------------------------------------------------------------------------
test_malloc_free 3

Free: number = 500000
Sleep 15 seconds, Output of 'top':
  3417 root      20   0   26692   5620   1224 S   0.0  0.1   0:00.55 mytest
----------------------------------------------------------------------------------------------
Now the process wil exit and die:
Output of 'top':
  3417 root      20   0   26692   5620   1224 S   0.0  0.1   0:00.55 mytest
-----------------------------------------------------------------------------------------------

在实验--2里:

1)我们用malloc分配一些内存空间(500,000个1KB),存入数据(全0),(分配空间,存入数据代码)

2)用free释放了500,000个1KB内存空间后(释放空间代码) ; 可以发现free后,返还了内存给操作系统

思考: 看来这个实验里,free立即把内存还给了操作系统。 在了解了glibc malloc/free的原理后,基于实验--2,我又设计了实验--3.

1.3 实验--3

[root@mydev-rosvile-redhat 0001]# ./mytest malloc-free-top-chunk
----------------------------------------------------------------------------------------------
test_malloc_free 1

At the beginning:
Output of 'top':
4017 root      20   0   26692   1536   1164 S   0.0  0.0   0:00.00 mytest
----------------------------------------------------------------------------------------------
test_malloc_free 2

Malloc: number = 500000
Output of 'top':
 4017 root      20   0  534496 513136   1220 S   0.0  6.4   0:00.25 mytest
----------------------------------------------------------------------------------------------
test_malloc_free 3

Free: number = 500000
Sleep 15 seconds, Output of 'top':
  4017 root      20   0  534496 513308   1224 S   0.0  6.4   0:00.35 mytest
 ----------------------------------------------------------------------------------------------
Now the process wil exit and die:
Output of 'top':
  4017 root      20   0  534496 513308   1224 S   0.0  6.4   0:00.35 mytest
-----------------------------------------------------------------------------------------------

在实验--3里:

1)在free了500,000个1KB内存空间后(相关代码) ,内存并没有返还给操作系统(占用513308 KB)

2)在程序将要退出前(相关代码), 内存仍然没有返还给操作系统(占用513308 KB)

3)和实验--2唯一的不同是: 实验--3故意malloc了1 Byte空间,却不去释放,(1 Byte memory leak代码)

思考:

这个行为和实验--1,map调用clear后,甚至map析构后,内存不返还给操作系统的行为是一样的。

为什么这1 Byte的故意的memory leak严重影响了free的行为?在第3节ptmalloc2中会展开讲述。

1.4 约定

因为用std::map做实验不够直观,所以后续大部分实验都直接基于malloc/free。

2. 内存分配基础知识

本章节部分节选自参考文献 1,淘宝工程师力作,有改动。

2.1 x86_64位下内存布局

图1 上图是 X86_64 下 Linux 进程的默认内存布局形式,这只是一个示意图, 当前内核默认 配置下,进程的stack和 mmap映射区域并不是从一个固定地址开始,并且每次启动时的值都 不一样, 这是程序在启动时随机改变这些值的设置,使得使用缓冲区溢出进行攻击更加困难。可以用如下命令禁止该特性:sudo sysctl -w kernel.randomize_va_space=0

2.2 内存分配/释放的相关系统调用和函数

从2.1中我们知道,heap和mmap region都是提供给用户程序的虚拟内存空间,那么如何获得该区域的内存呢?

对heap的操作,Linux提供了brk()系统调用,另外glibc对brk()进行了封装,提供了sbrk()函数; 对mmap region,Linux提供了mmap()和munmap()系统调用。

2.3 内存的延迟分配

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值