内存分配器ptmalloc2、tcmalloc、jemalloc,结构设计、内存分配过程详解

1. 引言

博主之前做过一个高并发内存池的项目实践,在实践中对于内存分配器的内存分配过程理解更加深刻了。在此期间,翻查了不少资料以及博客,发现源码分享的博客不多,能生动完整的讲述ptmalloc2、tcmalloc、jemalloc它们的结构设计以及内存分配过程的博客更是少之又少。那么在这篇文章中博主将总结分享内存分配器ptmalloc2、tcmalloc、jemalloc,欢迎大家留言讨论!

2. 现状

目前大部分服务端程序使用glibc提供的 malloc/free 系列函数,而glibc使用的 ptmalloc2 在性能上远远弱后于googletcmallocfacebookjemalloc。 而且后两者只需要使用LD_PRELOAD环境变量启动程序即可,甚至并不需要重新编译。

3. 业务场景

分配内存时进行系统调用的接口,对 heap 的操作, 操作系统提供了 brk() 系统调用,设置了Heap的上边界; 对 mmap 映射区域的操作,操作系统供了 mmap()munmap() 函数。

因为系统调用的代价很高,不可能每次申请内存都从内核分配空间,尤其是对于小内存分配。 而且因为 mmap 的区域容易被 munmap 释放,所以一般大内存采用 mmap(),小内存使用 brk()。

4. glibc ptmalloc2

最新版本:作为Glibc的默认内存分配器,ptmalloc2的更新与Glibc(目前更新到2.35+)版本绑定。截至当前,其核心设计仍基于最初的ptmalloc2架构, glibc 2.26(2017年)开始引入了线程本地缓存(tcache,Thread Local Caching)。

4.1 内存管理结构

线程本地缓存(tcache)
  • 作用:无锁快速分配小内存(每个线程独立)。

  • 管理范围:默认 ≤ 1032 字节(64 位系统)。

  • 数据结构

    • 64 个 tcache_bin(单链表,LIFO),每个 bin 对应一种固定大小(8B~1032B)。

    • 每个 bin 最多缓存 7 个 chunk(可通过 GLIBC_TUNABLES 调整)。

  • 优化点

    • 优先从 tcache 分配/释放,完全无锁。

    • 若 tcache 为空,则从 fastbins/smallbins 批量预加载(加锁)。

Fast Bins(快速通道)
  • 作用:缓存最近释放的小内存(仍保留 chunk 未合并,加速复用)。

  • 管理范围:≤ 160 字节(64 位系统)。

  • 数据结构:7 个单链表(LIFO),每个链表存储相同大小的 chunk。

  • 与 tcache 的关系

    • tcache 不足时从 fastbins 补充。

    • free() 时,小 chunk 可能先进入 tcache,若 tcache 满则进入 fastbins

Small Bins(小内存通道)
  • 作用:管理中等大小的内存(固定大小,合并友好)。

  • 管理范围:≤ 1008 字节(64 位系统)。

  • 数据结构:62 个双向链表(FIFO),每个 bin 对应一种固定大小(如 16B, 24B, ..., 1008B)。

  • 与 tcache 的关系

    • tcache 不足时从 smallbins 分配。

    • free() 时,若 chunk 大小匹配且 tcache 满,则进入 smallbins

Large Bins(大内存通道)
  • 作用:管理大内存块(范围区间,按大小排序)。

  • 管理范围:> 1008 字节。

  • 数据结构:63 个双向链表,每个 bin 管理一个大小范围(如 1024B~1088B, 1089B~1152B...)。

  • 分配策略

    • 查找最小满足大小的 chunk(可能分割)。

    • 若找不到,则进入 unsorted bin 或 top chunk

Unsorted Bin(临时缓冲区)
  • 作用:临时存放释放的 chunk,等待重新分类。

  • 数据结构:1 个双向链表(FIFO),存放最近释放的 chunk(不分大小)。

  • 分配流程

    • 分配时优先检查遍历 unsorted bin,若找到合适 chunk 直接返回,否则将其转移到 small/large bins

Top Chunk(堆顶块)
  • 作用:当前堆的顶部未分配内存,用于动态扩展。

  • 分配逻辑

    • 当所有 bins 无法满足请求时,从 Top Chunk 切割所需内存。

    • 若 Top Chunk 不足,通过 sbrk(主分配区)或 mmap(非主分配区)扩展堆空间。

mmap 通道(超大内存)
  • 管理大小:默认 ≥ 128KB(可通过 M_MMAP_THRESHOLD 调整)。

  • 特点

    • 直接通过 mmap 分配独立内存段,绕过堆管理。

    • 释放时通过 munmap 立即归还系统,避免碎片。

last remainder chunk(最后剩余块)
  • 优化连续内存分配:当用户请求的内存略小于某个空闲块时,分割后剩余的碎片会被标记为 last remainder,优先用于后续的连续分配请求。

  • 减少外部碎片,提升内存利用率。

4.2 内存分配流程

4.3 多线程支持

  • 主分配区(Main Arena):主线程默认使用,通过 brk 扩展堆。

  • 非主分配区(Thread Arena):其他线程按需创建(数量受 MALLOC_ARENA_MAX 限制)。

  • 锁优化

    • 每个分配区有独立的锁,线程优先访问绑定的分配区。

    • 若所有分配区被占用,线程会竞争主分配区。

4.4 缺点

  • 内存碎片:外部和内部碎片问题突出,尤其长期运行后。
  • 锁竞争:尽管有 tcache 和分配区,高并发下仍可能成为瓶颈。
  • 释放延迟:fastbins 延迟合并,brk 堆收缩不积极。
  • 场景局限:不适合高频小对象分配或实时系统。

5. tcmalloc

最新版本:截至2025年,tcmalloc已迭代至支持per-CPU模式的优化版本(如v2.10+),进一步提升多核性能。场景适用于多核服务器(如 Web 服务、数据库),高频小对象分配(如微秒级响应的 RPC 框架)。

5.1 内存管理结构

CPU Local Cache(CPU 本地缓存)

  • 申请对象为小内存(通常 ≤256KB)。

  • 使用Size Class 哈希桶,每个 CPU 核心维护一组自由链表(FreeList),按大小分类(如 8B、16B、...、256KB)。

  • 动态批量填充,当本地缓存不足时,从下层批量获取对象(类似慢启动算法)。

  • 这里无锁

Central Free List(中心空闲列表)
  • 这一层全局共享,作为 CPU 本地缓存的后备仓库。

  • 结构为哈希桶Span 链表:按大小分类管理内存块(Span),每个 Span 被切分为多个小对象。

  • 轻量级锁:仅当 CPU 缓存需要补充时加锁(锁粒度细化到 Size Class)。

  • 申请内存,当CPU 缓存耗尽时,从 Central Free List 批量拉取对象(如一次获取 32 个 16B 的块)。Central Free List 自身从 PageHeap 申请新的 Span。

PageHeap(页堆)

  • 以页(通常 8KB)为粒度的大内存。

  • 合并相邻空闲页,减少外碎片。

  • 基数树(Radix Tree)算法,快速映射内存页到 Span,支持 O(1) 查找。
  • 超过 256KB 的请求直接由 PageHeap 处理(通过 mmap 或 VirtualAlloc)。

5.2 内存分配流程(Per-CPU 模式)

  1. 小内存分配(≤256KB)时,线程先获取当前 CPU 的本地缓存(GetThisCPUCache()),根据请求大小找到对应的 Size Class 自由链表。若链表非空,直接弹出对象返回(无锁),若链表为空,从 Central Free List 批量获取对象(加锁,但频率低)。
  2. 小内存释放时,将对象放回当前 CPU 的本地缓存自由链表(无锁)。若本地缓存超过阈值(如 1024 个对象),触发批量归还到 Central Free List。

  3. 大内存分配(>256KB)时,直接由 PageHeap 分配,通过 mmap 或 VirtualAlloc 申请大块内存。
  4. 大内存释放时通过 PageHeap 的基数树找到对应 Span,标记为空闲。尝试合并相邻空闲 Span,形成更大的连续内存。

5.4 局限性

它是零锁竞争的,Per-CPU 缓存完全无锁,适合超高并发场景。本地化操作减少 CPU 缓存行失效(Cache Line Ping-Pong),所以它延迟低。支持弹性扩展,CPU 数量增加时,性能线性提升(无全局瓶颈)。

Per-CPU 缓存可能暂存未使用的对象(可通过调优阈值缓解),造成内存浪费。它对CPU比较依赖,若线程频繁迁移 CPU,性能会下降(需绑定线程到核心)。

6. jemalloc

最新版本jemalloc 5.3.0(发布于2025年5月1日),进一步优化内存碎片管理和多线程扩展性。

6.1 内存管理结构

之前的版本

在之前的版本中内存被划分成若干个arena;每个arena被划分成若干个chunk(每个默认为4M);每个chunk被划分成若干个run;每个run由若干个page组成(每个page默认为4K),同一个run中的page又被细分成若干个大小相同的region;

为了减少内存碎片及快速定位到合适大小的内存,jemalloc将run按以下class_size分成44类(同一个run下的region大小相同):

  • small(<4K): [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]

  • large(4K-4M): [4K, 8K, 12K, …, 4072K]

  • huge(>4M): [4M, 8M, 12M, …]

  • 对于小于4K内存的申请,jemalloc直接向上取整到最小的class_size,例如申请1-7字节,都会分配一个8字节内存。

新版本

在 jemalloc 5.3.0 中,旧版的 chunkrun 等概念被 Extent 统一取代,其核心设计如下:

1. Extent(扩展块)
  • 表示连续的虚拟内存区域(大小灵活,通常为 2MB 或 4MB,可动态调整)。替代旧版的 chunk,支持更细粒度的拆分与合并。

  • 状态分级:分为 Active(在用)、Dirty(已释放但未擦除)、Muzzy(部分擦除)、Retained(完全擦除保留)。

  • 动态管理:支持按需分割为 Slab 或直接分配。

2. Slab(内存板)
  • 将 Extent 划分为固定大小的槽位(如 32B、64B),通过位图管理分配状态。替代旧版 run 的功能,但更轻量化。

  • 按需分配:仅在使用时初始化 Slab,减少内存占用。

  • 延迟释放:空闲 Slab 不会立即合并,保留在 Dirty/Muzzy 状态供快速复用。

3. Page(页)
  • 保留它的意义,仍作为操作系统内存管理的最小单位(如 4KB),但 jemalloc 内部通过 Extent 聚合多页。

  • 用户无需关注页级操作,所有分配均通过 Extent 和 Slab 抽象。

4. Region(区域)
  • 旧版中模糊的 region 概念被 Extent 和 Slab 明确取代,不再存在于官方文档。

6.2 内存分配流程

1. 从线程缓存(Thread Cache)分配
  • 当请求的内存大小 ≤ 14KB(小对象)。线程本地缓存(TLS)中对应的 Size Class 有空闲槽位。根据请求大小匹配对应的 Size Class(如 32B、64B),从线程本地的 Slab 空闲链表 中弹出第一个空闲槽位,直接返回槽位内存地址(无锁操作)。

  • 这个过程是零锁竞争,速度极快(纳秒级)。适合高频小对象分配(如短生命周期对象)。

2. 从 Arena 分配
  • 当线程缓存为空,且请求大小 ≤ 4MB(中等对象)。或请求大小 >14KB 但 ≤4MB(跳过线程缓存)。

  • 线程绑定到特定 Arena(默认轮询或哈希分配)。若当前 Arena 锁竞争激烈,可能创建新 Arena。从 Arena 的 Extent/Slab 分配,如果是中等对象(14KB < size ≤4MB),直接分配整个 Extent 或从空闲 Extent 链表分割。如果是小对象(≤14KB):从 Arena 的 Slab 中批量获取多个槽位,填充线程缓存后返回一个。

  • 锁机制:Arena 内部使用细粒度锁(如每个 Extent 独立锁),减少竞争。

3. 从系统内存(mmap/brk)分配
  • 当我们请求大小 >4MB(大对象)或 Arena 的 Extent 不足(需扩展堆空间)时。直接调用 mmap,记录元信息到全局基数树(Radix Tree)。通过 mmap 申请独立内存段(默认 ≥4MB)。这时是大对象,不进入线程缓存或 Arena,直接由全局结构跟踪。释放时调用 munmap 立即归还系统,避免碎片。

  • 可以避免污染线程缓存和 Arena。减少大内存的合并开销。

6.3 内存回收机制

1. 线程本地缓存(Thread Cache)的回收

  • 用户释放的小对象(≤4KB)首先存入线程本地缓存(Thread Cache)的对应 size class 槽位。

  • 批量回收每个 size class 的 Thread Cache 槽位有最大保留数量(如默认 200 个)。当超出阈值时,jemalloc 将多余的槽位批量归还到全局的 Arena

2. 全局分配区(Arena)的回收

  • 每个 Arena 维护一组 extent(如 4MB 的大块内存),按 size class 分类。Extent 分割为 slab 后,jemalloc 跟踪每个 slab 中已分配和空闲的槽位(Slot)。

  • Slab 完全空闲时,即当某个 slab 的所有槽位均被释放时,其所属的 Extent 可能被标记为空闲。jemalloc 定期内存压力检测或根据系统内存压力,合并空闲 Extent 并归还操作系统。

3. Extent 的合并与归还

  • 惰性合并(Lazy Coalescing)jemalloc 不会立即合并相邻的空闲 Extent,而是保留它们以便快速响应未来可能的相同大小请求。当连续的空闲 Extent 达到一定数量或系统内存不足时,主动合并以减少外部碎片。

  • 操作系统归还完全空闲的 Extent 可能通过 munmap 归还操作系统。jemalloc 默认保留部分空闲 Extent(通过 retain:true 配置),避免频繁的 mmap/munmap 系统调用开销。

6.4 局限性

  • 内存碎片积累(尤其多 Arena 场景)。
  • 线程本地缓存囤积 导致内存利用率下降。
  • 超大对象管理开销 和 配置复杂度高
  • 平台兼容性差异 和 调试困难

参考资料:

内存优化总结:ptmalloc、tcmalloc和jemalloc_ptmalloc jemalloc tcmalloc-CSDN博客

ptmalloc VS tcmalloc VS jemalloc 原理及对比测试

ptmalloc代码浅析2(small bin/large bin结构图)_largebin-CSDN博客


本篇文章对内存分配器ptmalloc2、tcmalloc、jemalloc,结构设计、内存分配过程做了详解!欢迎留言讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/80980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【拥抱AI】Deer-Flow字节跳动开源的多智能体深度研究框架

最近发现一款可以对标甚至可能超越GPT-Researcher的AI深度研究应用&#xff0c;Deer-Flow&#xff08;Deep Exploration and Efficient Research Flow&#xff09;作为字节跳动近期开源的重量级项目&#xff0c;正以其模块化、灵活性和人机协同能力引发广泛关注。该项目基于 La…

openfeign与dubbo调用下载excel实践

一、前言 openfeign和dubbo均是rpc框架 RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;框架 是一种允许程序像调用本地方法一样调用远程服务器上函数的技术。它隐藏了底层网络通信的复杂性&#xff0c;让开发者可以专注于业务逻辑&#xff0c;实现…

解密企业级大模型智能体Agentic AI 关键技术:MCP、A2A、Reasoning LLMs-强化学习算法

解密企业级大模型智能体Agentic AI 关键技术&#xff1a;MCP、A2A、Reasoning LLMs-强化学习算法 现在我们的核心问题是有一些同学会知道要才能强化学习。为什么才能强化学习&#xff1f;是实现AGI。例如从这个其实你从第一阶段开始以后&#xff0c;就是chatbot&#xff0c;这…

音频分类的学习

1.深度学习PyTorch入门-语音分类 https://blog.csdn.net/sinat_41787040/article/details/129795496 https://github.com/musikalkemist/pytorchforaudio https://github1s.com/musikalkemist/pytorchforaudio/blob/main/04%20Creating%20a%20custom%20dataset/urbansoundda…

美SEC主席:探索比特币上市证券交易所

作者/演讲者&#xff1a;美SEC主席Paul S. Atkins 编译&#xff1a;Liam 5月12日&#xff0c;由美国SEC加密货币特别工作组发起的主题为《资产上链&#xff1a;TradFi与DeFi的交汇点》系列圆桌会议如期举行。 会议期间&#xff0c;现任美SEC主席Paul S. Atkins发表了主旨演讲。…

Qt file文件操作详解

1.引言 很多应用程序都具备操作文件的能力&#xff0c;包括对文件进行写入和读取&#xff0c;创建和删除文件等等&#xff0c;甚至某些应用程序的就是为了操作文件&#xff0c;像WPS Office。基于此Qt框架中专门提供了对文件操作的类&#xff1a;QFile。 2.QFile文件操作 QF…

【测试开发知识储备】之Jacoco(Java Code Coverage)

文章目录 Jacoco是什么Jacoco的主要功能&#xff08;一&#xff09;多样化覆盖率指标分析&#xff08;二&#xff09; 丰富的报告生成&#xff08;三&#xff09;实时数据收集 Jacoco的工作原理&#xff08;一&#xff09;字节码增强&#xff08;二&#xff09;测试执行与数据收…

Docker 介绍与使用

Docker 文章目录 Docker介绍与虚拟机的比较启动速度占用资源 优势更容易迁移更容易维护更容易扩展 使用场景持续集成提供可伸缩的云服务搭建微服务架构 镜像与容器镜像构成&#xff08;分层结构&#xff09;镜像与容器的区别 安装 Docker常用命令介绍镜像相关容器相关 实战&…

《AI大模型应知应会100篇》第62篇:TypeChat——类型安全的大模型编程框架

第62篇&#xff1a;TypeChat——类型安全的大模型编程框架 摘要 在构建 AI 应用时&#xff0c;一个常见的痛点是大语言模型&#xff08;LLM&#xff09;输出的不确定性与格式不一致问题。开发者往往需要手动解析、校验和处理模型返回的内容&#xff0c;这不仅增加了开发成本&a…

upload-labs通关笔记-第5关 文件上传之.ini绕过

目录 一、ini文件绕过原理 二、源码审计 三、渗透实战 1、查看提示 2、制作.user.ini文件 &#xff08;1&#xff09;首先创建一个文本文件 &#xff08;2&#xff09;保存文件名为.user.ini 2、制作jpg后缀脚本 &#xff08;1&#xff09;创建一个文本文件 &#xf…

为什么 Linux 上默认没有 host.docker.internal

在 Linux 环境中&#xff0c;host.docker.internal 是 Docker 为容器提供的一个特殊 DNS 名称&#xff0c;用于指向宿主机的 IP 地址&#xff08;类似 macOS/Windows 中的行为&#xff09;。但这个功能在 Linux 上默认不启用&#xff0c;需要手动配置才能使用。以下是详细解释和…

C++GO语言微服务和服务发现②

01 创建go-micro项目-查看生成的 proto文件 02 创建go-micro项目-查看生成的main文件和handler ## 创建 micro 服务 命令&#xff1a;micro new --type srv test66 框架默认自带服务发现&#xff1a;mdns。 使用consul服务发现&#xff1a; 1. 初始consul服务发现&…

Redis--常见数据类型List列表

目录 一、概念 二、命令 2.1 LPUSH 2.2 LPUSHX 2.3 RPUSH 2.4 RPUSHX 2.5 LRANGE 2.6 LPOP 2.7 RPOP 2.8 LINDEX 2.9 LINSERT 2.10 LLEN 2.11 阻塞版本命令 三、内部编码 一、概念 列表类型是用来存储多个有序的字符串&#xff0c;列表中的每个字符串称为元素&…

QListWedget控件使用指南

QListWedget公共函数 函数签名功能描述QListWidget(QWidget *parent nullptr)构造函数&#xff0c;创建一个QListWidget对象&#xff0c;可指定父部件&#xff08;默认为nullptr&#xff09;。virtual ~QListWidget()虚析构函数&#xff0c;释放QListWidget对象及其资源。voi…

Seata源码—1.Seata分布式事务的模式简介

大纲 1.Seata分布式事务框架简介 2.Seata AT模式实现分布式事务的机制 3.Seata AT模式下的写隔离机制 4.Seata AT模式下的读隔离机制 5.官网示例说明Seata AT模式的工作机制 6.Seata TCC模式的介绍以及与AT模式区别 7.Seata Saga模式的介绍 8.单服务多个库的分布式事务…

【Qt】之音视频编程2:QtAV的使用篇

QtAV 基本播放控制功能实现&#xff08;C & QML&#xff09; QtAV 提供了完整的播放控制 API&#xff0c;支持 播放、暂停、停止、快进快退、截屏 等功能。以下是具体实现方法&#xff1a; 1. C 控制方式 基本播放控制 #include <QtAV> #include <QtAV/AVPlaye…

歌词滚动效果

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><!-- 设置标签页图标 --><link rel"shortcut icon&…

基于大模型的TIA诊疗全流程智能决策系统技术方案

目录 一、多模态数据融合与预处理系统1.1 数据接入模块1.2 数据预处理伪代码二、TIA智能预测模型系统2.1 模型训练流程2.2 混合模型架构伪代码三、术中智能监测系统3.1 实时监测流程3.2 实时预测伪代码四、智能诊疗决策系统4.1 手术方案推荐流程4.2 麻醉方案生成伪代码五、预后…

Java 日期解析与格式化:从标准格式到自然语言解析

使用 Java 搭配 Apache Commons Lang3 和 Natty 库&#xff0c;实现灵活高效的日期解析与格式化。 一、背景 将不同格式的日期统一成一个格式。日期格式可能有以下几种类型&#xff1a; 标准格式&#xff1a;2024-02-28、14/05/2022、2002年5月6日非英文月份缩写&#xff1a;…

Room持久化库:从零到一的全面解析与实战

简介 在Android开发中,Room作为官方推荐的数据库持久化库,提供了对SQLite的抽象层,使得数据库操作更加安全、高效且易于维护。 Room通过注解处理器和编译时验证,显著降低了数据库操作的复杂度,同时支持响应式编程模式,使开发者能够轻松实现数据变化的实时监听。对于企业…