glibc 内存管理简记

glibc 内存管理

最近在项目中遇到了一个内存涨的问题,每次连接数据库操作查询后内存都会涨,经分析发现是因为:程序中有段代码执行后,进行上百万次malloc,然后再依次free,但进程内存没有下降。
在查找了glibc的相关资料后发现,glibc 的free 的释放内存并不是把内存还给操作系统而是放入bin(free list),同时每个线程都会维护自己的堆与free list(在核心数足够的情况下)。每个线程分配的内存由每个线程自己负责,在有多个线程(如:线程池)时导致内存会上涨很多。下面简单记录下glic的内存管理。

概述

linux 内存布局如下(32位)
glibc 内存管理简记

random offset 取决于是否开启ASLR(一种针对缓冲区溢出的安全保护技术)。r若不开启则为0。

malloc通过brk或mmap进行内存分配。

brk: brk obtains memory (non zero initialized) from kernel by increasing program break location (brk). Initially start (start_brk) and end of heap segment (brk) would point to same location.

mmap: malloc uses mmap to create a private anonymous mapping segment. The primary purpose of private anonymous mapping is to allocate new memory (zero filled) and this new memory would be exclusively used by calling process.

  • brk 通过向上移动brk指针,来分配内存
  • mmap 是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存

当用户请求超过 128KB(比如 malloc(132*1024)) 大小并且此时 arena 中没有足够的空间来满足用户的请求时,内存将通过 mmap 系统调用(不再是 sbrk)分配,而不论请求是发自 main arena 还是 thread arena。 非主线程通过mmap分配内存

arena

系统分配并由glic管理的堆内存叫作arena,main thread 和其他 thread 都有自己的 arena (如果足够),arena的个数与系统的处理器核心个数相关,如下所示:

For 32 bit systems:
     Number of arena = 2 * number of cores + 1.
For 64 bit systems:
     Number of arena = 8 * number of cores + 1.

当arena不够时,复用已使用的arena

假设有如下情境:一台只含有一个处理器核心的PC机安装有32位操作系统,其上运行了一个多线程应用程序,共含有4个线程——主线程和三个用户线程。显然线程个数大于系统能维护的最大arena个数(2*核心数 + 1= 3),那么此时glibc malloc就需要确保这4个线程能够正确地共享这3个arena,那么它是如何实现的呢?

当主线程首次调用malloc的时候,glibc malloc会直接为它分配一个main arena,而不需要任何附加条件。

当用户线程1和用户线程2首次调用malloc的时候,glibc malloc会分别为每个用户线程创建一个新的thread arena。此时,各个线程与arena是一一对应的。但是,当用户线程3调用malloc的时候,就出现问题了。因为此时glibc malloc能维护的arena个数已经达到上限,无法再为线程3分配新的arena了,那么就需要重复使用已经分配好的3个arena中的一个(main arena, arena 1或者arena 2)。那么该选择哪个arena进行重复利用呢?

1)首先,glibc malloc循环遍历所有可用的arenas,在遍历的过程中,它会尝试lock该arena。如果成功lock(该arena当前对应的线程并未使用堆内存则表示可lock),比如将main arena成功lock住,那么就将main arena返回给用户,即表示该arena被线程3共享使用。

2)而如果没能找到可用的arena,那么就将线程3的malloc操作阻塞,直到有可用的arena为止。

3)现在,如果线程3再次调用malloc的话,glibc malloc就会先尝试使用最近访问的arena(此时为main arena)。如果此时main arena可用的话,就直接使用,否则就将线程3阻塞,直到main arena再次可用为止。

这样线程3与主线程就共享main arena了。至于其他更复杂的情况,以此类推。

arena 结构图示如下:

glibc 内存管理简记

glibc 内存管理简记

注意:

  • Main arena 无需维护多个堆,因此也无需 heap_info。当空间耗尽时,与 thread arena 不同,main arena 可以通过 sbrk 拓展堆段,直至堆段「碰」到内存映射段;

  • 与 thread arena 不同,main arena 的 arena header 不是保存在通过 sbrk 申请的堆段里,而是作为一个全局变量,可以在 libc.so 的数据段中找到

chunk

arena由chunk组成
chunk 有4中类型:

  1. allocated chunk
  2. free chunk
  3. top chunk
  4. last remained chunk

下面主要介绍下1和2

allocated chunk

glibc 内存管理简记

如果前一个chunk是free ,则第一部分表示前一个chunk的大小,如果为allocated ,则这部分属于前一chunk的user data。(复用减少内存开销)

  • PREV_INUSE§: 表示前一个chunk是否为allocated。
  • IS_MMAPPED(M):表示当前chunk是否是通过mmap系统调用产生的。
  • NON_MAIN_ARENA(N):表示当前chunk是否是thread arena

free chunk

glibc 内存管理简记

结构与allocated chunk 类似,不过多了fd,bk指针 用来串联free list。

top chunk

当一个chunk处于一个arena的最顶部(即最高内存地址处)的时候,就称之为top chunk。该chunk并不属于任何bin,而是在系统当前的所有free chunk(无论那种bin)都无法满足用户请求的内存大小的时候,将此chunk当做一个应急消防员,分配给用户使用。如果top chunk的大小比用户请求的大小要大的话,就将该top chunk分作两部分:1)用户请求的chunk;2)剩余的部分成为新的top chunk。否则,就需要扩展heap或分配新的heap了——在main arena中通过sbrk扩展heap,而在thread arena中通过mmap分配新的heap。

bins

3

参考

  1. Linux堆内存管理深入分析
  2. Syscalls used by malloc
  3. 理解 glibc malloc:主流用户态内存分配器实现原理 原文:Understanding glibc malloc