作者|daydreamer第一篇《探秘C++内存管理(理论篇)》主要介绍了LinuxC++程序内存管理的理论基础。本篇作为系列文章《探秘C++内存管理》的第二篇,将探讨经典的内存管理器ptmalloc是如何管理C++程序的内存的。通过分析ptmalloc解决问题的重点和设计与实现成本的权衡,更具体地呈现了C++内存管理所面临的问题以及在项目实现中的巧妙之处。一、概述ptmalloc是开源GNUC库(glibc)默认的内存管理器。目前大多数Linux服务器程序都使用ptmalloc提供的malloc/free系列函数,其性能远不如Meta的jemalloc和Google的tcmalloc。服务器端程序调用ptmalloc提供的malloc/free函数来申请和释放内存。ptmalloc提供了对内存的集中管理,尽可能做到:用户申请和释放内存更高效,避免多线程内存申请并发和锁寻求与操作系统期间内存使用和malloc/free性能消耗之间的平衡点交互过程减少内存碎片,不频繁调用系统调用函数。ptmalloc的内存管理策略简述:提前向操作系统申请并持有一块内存给用户malloc,同时管理用户在已用和空闲内存上执行free,管理回收的内存,并执行管理策略,决定是否返回给操作系统执行使用(以32位机为例)。2.内存管理2.1数据结构为了解决多线程锁争用问题,将内存分配分为主分配区(main\_area)和非主分配区(no\_main\_area)。同时,为了方便内存管理,使用边界标记的方法将预申请的内存分成很多块(chunk);在ptmalloc内存分配器中,malloc\_chunk是管理不同类型chunk的基本组织单元,功能和大小相似的chunk被串接成一个链表,称为bin。main\_arena和non\_main\_arena主分配区和非主分配区形成一个循环链表进行管理,每个分配区使用互斥锁实现线程访问分配区的互斥。每个进程只有一个主分配区,但允许有多个非主分配区,非主分配区的数量只能增加不能减少。主分配区可以访问堆区和进程的mmap映射区,即主分配区可以使用sbrk()和mmap()分配内存;非主分配区只能使用mmap()分配内存。不同arena的管理策略大致如下:分配内存时检查线程私有变量中是否已经有分配区并加锁,如果加锁成功则使用分配区分配内存;如果没有找到分区或者加锁失败,则遍历循环链表得到一个未加锁的分配区。如果整个循环链表中没有未锁定的分配区,则新开一个分配区,加入到循环链表中并锁定,使用这个分配区满足当前线程的内存分配,要释放内存,先获取待释放内存块所在分配区的锁。如果其他线程正在使用分配区,则等待其他线程释放分配区的互斥锁,然后释放主内存分配区和非主分配区的结构如下:其中fastbinsY和bins实际内存块的管理和操作结构:fastbinsY:用来保存fastbinsbins[NBINS*2-2]:unsortedbin(1,bin[1]),Acollectionofsmallbins(62,bin[2]~bin[63]),largebins(63,bin[64]~bin[126]),共126个条目(NBINS=128),bin[0]和bin[127]没有使用malloc\_chunk和binsptmalloc统一管理heap和mmap映射区的idlechunk。当用户提出分配请求时,他们会首先尝试在空闲块中寻找并拆分,从而避免频繁的系统调用以减少内存分配开销。为了更好地管理和查找空闲块,在预分配空间前后添加了必要的控制信息。内存管理结构malloc\_chunk的成员和函数如下:mchunk_prev_size:上一个freechunk的大小mchunk_size:当前chunk的大小必要的属性flags:上一个chunk正在使用中(P=1)当前chunk为mmap映射区分配(M=1)或堆区分配(M=0)当前chunk属于非主分配区(A=0)或非主分配区(A=1)fd和bk:chunk块空闲时存在,用于将空闲chunk块添加到空闲chunk块链表中,统一管理。根据chunk的大小和用途,分为以下几种bins:fastbinsFastbins只保存一个小的heap,使用单链表串联。在链表头部添加和删除chunk,进一步提高了小内存的分配效率。fastbins记录大小递增8字节的bin链表,一般不会与其他堆块合并。unsortedbinsmallbins和largebins缓冲区用于加速分配。chunk大小没有大小限制,用户释放的heapblock会先进入unsortedbin。在分配堆块时,会先检查unsortedbin链表中是否有合适的堆块,切割后返回。Smallbins存储大小小于512B的块的bins称为smallbins。smallbins每个bin之间相差8个字节,同一个smallbin中的chunk大小相同,使用双向循环链表串联。Largebins存储大小>=512B的块的bins称为largebins。largebins中的每个bin都包含给定范围内的一个chunk,其中chunks大小从大到小排序,大小相同的从时间到大排序。当然,并不是所有的块都是按上述方式组织的。其他常用的chunk,如:topchunk:分配区顶部的空闲内存。当bins不能满足内存分配要求时,会尝试在topchunk中分配。当topchunk>userrequestsize时,topchunk会分为用户请求大小(userchunk)和剩余topchunk大小(remainderchunk)两部分。当topchunk
