1. 堆内存
1.1 概述
进程的虚拟内存空间中,堆是一个由分配器动态分配的、由低地址向高地址增长、非连续存储的线性内存区域。在典型的Linux 32/64 位内存布局中,堆位于未初始化数据段(BSS 段) 之上。
堆内存通常由程序员手动申请和释放,若程序员不释放,程序结束时由操作系统回收,且分配速度慢,容易产生内存碎片。
堆内存区域主要用于存放生命周期不确定的对象实例或大型数据结构。堆空间是线程共享的内存区域,所有线程均可访问堆中的对象。堆内存通常由程序在运行时动态申请,因此比栈空间更加灵活。
1.2 堆内存特点
- 1、动态灵活:堆内存的大小在程序编译时并不确定,而是在程序运行过程中根据需要动态申请的;
- 2、手动或自动管理:C/C++语言中,堆内存由程序员手动申请和释放,而在Java语言中堆内存由垃圾回收器(GC)自动管理,定期清理不再被引用的对象;
- 3、线程共享:堆内存是所有线程共有的区域,因此多线程同时在堆中申请空间时,需要通过加锁或 TLAB(线程本地分配缓存)等机制来保证线程安全;
- 4、非连续存储:堆在内存中通常是非连续的树形或链式结构,这种随机分布的方式虽然灵活,但容易产生内存碎片,堆主要用于存放构造复杂的引用类型数据,如对象实例、数组、集合等;
- 5、生命周期不固定:程序员通过调用malloc或new等函数显式申请堆内存;一旦分配成功,该堆内存块在被显式释放之前将一直保持有效,不受函数调用范围的限制;该内存块需要通过free或delete释放,若未释放,该内存直到进程结束才会被系统回收,此现象称为内存泄漏;
1.3 堆和栈的区别
| 特性 | 堆 | 栈 |
| 分配方式 | 动态分配(运行时决定) | 编译时或进入函数时分配 |
| 管理主体 | 程序员手动管理或GC | 编译器自动管理 |
| 内存容量 | 通常很大 | 通常很小(几M) |
| 碎片化 | 内部碎片 | 无碎片 |
| 访问速度 | 较慢 | 快(寄存器传参) |
2. 堆内存管理器
2.1 堆管理器的原理
堆内存管理器也称为堆内存分配器,主要负责管理程序动态分配内存区域,执行内存分配、使用跟踪和释放操作。
堆内存管理器维护一个可用的内存池,并采用各种算法高效地分配和回收内存。当接受到一个分配请求时,堆内存管理器会采用特定的策略选择一个合适的空闲内存块供程序使用。
堆内存管理器通常不会由操作系统直接实现,而是作为用户空间库的一部分提供,比如glibc库中的ptmalloc2管理器。
2.2 堆内存管理器的功能
堆内存管理器是位于应用程序与操作系统内核之间的软件层。主要功能如下:
- 1、响应申请与分配:当程序请求内存(如malloc或new)时,堆内存管理器负责从空闲列表中根据算法查找并提供一块合适大小的可用内存块;
- 2、标记记录:通常使用链表等数据结构来管理空闲内存地址,记录哪些内存块正在使用;
- 3、释放堆内存:当程序不再需要某块堆内存时,则将其标记为空闲,并将其添加回各种管理容器(如glibc的bins)中,而不是直接还给操作系统;
- 4、内存合并与碎片整理:堆内存管理器检查新释放块的物理邻居是否也为空闲。如果是,则将其与相邻的空闲块合并以形成更大的可用块,以减少内存碎片;
3. ptmalloc2分配器
ptmalloc2(即Pthreads Malloc 2) 是GNU C库 (glibc) 默认的动态内存分配器,广泛应用于Linux系统中,它在经典的dlmalloc基础上增加了对多线程并发分配的优化支持。
3.1 ptmalloc2的核心机制
- 1、多分配区: 这是ptmalloc2解决线程安全的核心机制。分配区(Arenas): 这是ptmalloc2解决线程安全的核心机制。它不使用一个全局锁来同步所有内存操作,而是允许每个线程拥有或使用一个独立的内存区域(称为“分配区”)。不同线程可以在各自的分配区内并行执行内存分配和释放操作,从而显著减少锁竞争,提高并发性能。
主分配区:程序启动时创建的第一个分配区,可以访问进程的堆区(通过sbrk系统调用)和mmap区域;
非主分配区:当新的线程需要内存且主分配区被锁定时,ptmalloc2会创建新的非主分配区。这些分配区仅通过mmap向操作系统“批发”大块内存(通常是64MB 或128MB,具体取决于架构)来管理各自的子堆;
- 2、内存块:内存以“块”为单位进行管理。每个分配给用户的内存块都包含额外的元数据(如大小、前后块状态等)。
- 3、分箱管理:ptmalloc2使用不同类型的箱子(Bins)来管理空闲内存块(chunks),根据大小进行分类以加速查找合适大小的块,避免频繁的系统调用,主要包括:
Fast Bins: 存放非常小的、最近释放的内存块(通常小于128字节),采用LIFO(后入先出)单向链表,查找和释放速度极快,但不合并相邻块;
Small Bins: 存放固定大小的小块内存(通常小于1024字节),采用FIFO(先入先出)双向链表,同尺寸的块放在一个箱内。
Large Bins: 存放较大的内存块,每个箱存放一个范围大小的块,并按大小排序,以实现“最佳适配”而非“首次适配”,减少碎片。
Unsorted Bin: 缓存最近释放的块作为一个临时的中转区域,所有非Fast Bins的空闲块首先进入此区域,后续的分配请求会首先在此区域查找或进行合并操作,以提高效率;也就是说,在分配时,如果 tcache 和 fastbins 没命中,会先扫描这里。
- 4、TCache (Thread Local Cache): 在现代 glibc 版本中引入,为每个线程提供了一个本地缓存,用于存储特定大小的小块内存。线程可以直接从 TCache 获取内存而无需加锁或访问共享的 Arena 结构,进一步提高了性能。
3.2 ptmalloc2的功能
ptmalloc2的功能主要有多线程优化、内存分级缓存与管理、系统调用接口适配、碎片整理与空间复用等:
- 1、为了解决传统分配器在多线程竞争下的性能瓶颈,ptmalloc2 引入了分配区(Arenas)功能;
- 2、ptmalloc2使用名为Bins的链表结构来组织空闲内存块(chunks),从而实现超快速的分配和回收;
- 3、ptmalloc2负责充当应用程序与Linux内核之间的桥梁,根据请求大小自动选择最合适的内核接口,brk/sbrk用于小内存申请;而mmap 针对大块内存(默认阈值通常为128KB),直接向系统请求独立的匿名内存映射区,这种方式分配的内存释放后会立即归还给操作系统;
- 4、当一个内存块被释放时,ptmalloc2会检查其前后相邻块是否也为空闲。如果是,则将它们合并成一个大块,防止内存变得过于破碎而无法满足大的分配需求;
- 5、当堆顶部的空闲空间超过一定阈值时,ptmalloc2会主动调用sbrk缩小堆大小,将物理内存还给操作系统;
1. 一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。