强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

jemalloc 内存分配器完全指南 / 01 - jemalloc 概述

第 1 章:jemalloc 概述

1.1 什么是 jemalloc

jemalloc(Jason’s Efficient Malloc)是一款通用的动态内存分配器,由 Jason Evans 于 2005 年为 FreeBSD 操作系统开发。其名称来源于作者名字的首字母缩写。

发展历程

年份里程碑
2005Jason Evans 开始为 FreeBSD 开发 jemalloc
2006发表论文 “A Scalable Concurrent malloc(3) Implementation for FreeBSD”
2007成为 FreeBSD 7.0 的默认分配器
2009Facebook 采用 jemalloc 作为其服务端默认分配器
2011Redis 将 jemalloc 作为默认内存分配器
2015Rust 选择 jemalloc 作为标准库默认分配器(后在 1.28 改为系统分配器)
2017jemalloc 5.0 发布,大幅改进性能和配置接口
2022jemalloc 5.3.0 发布,持续优化

核心特性

  1. Arena 分区管理:将内存划分为多个 Arena,减少线程间锁竞争
  2. Thread-Local Cache (TC):每个线程拥有本地缓存,快速分配小对象
  3. 大小类 (Size Class):按固定大小类别管理内存,减少碎片
  4. Slab 分配:将页 (Page) 切分为等大小的块 (Run),高效管理小对象
  5. Heap Profiling:内置内存分析工具,支持采样和泄漏检测
  6. 运行时可调:通过环境变量或配置文件在运行时调整参数

1.2 为什么需要 jemalloc

系统默认 malloc 的问题

Linux 系统默认使用 glibc 的 ptmalloc,其设计源于 Doug Lea 的 dlmalloc。虽然它在单线程场景下表现尚可,但在现代高并发环境中存在以下问题:

问题说明
锁竞争严重多线程共享同一个 arena,频繁的 mutex 操作导致性能瓶颈
内存碎片长期运行后,RSS(常驻内存集)持续增长
归还困难已分配内存难以及时归还给操作系统
缺乏监控无内置的内存分析和调试能力
扩展性差线程数增加时,性能显著下降

一个简单的实验

以下代码演示了 glibc malloc 与 jemalloc 在多线程场景下的性能差异:

// malloc_bench.c - 多线程内存分配基准测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>

#define NUM_THREADS   8
#define ALLOC_COUNT   100000
#define ALLOC_SIZE    256

static void *thread_func(void *arg) {
    void *ptrs[ALLOC_COUNT];
    for (int i = 0; i < ALLOC_COUNT; i++) {
        ptrs[i] = malloc(ALLOC_SIZE);
        if (ptrs[i]) memset(ptrs[i], 0xAB, ALLOC_SIZE);
    }
    for (int i = 0; i < ALLOC_COUNT; i++) {
        free(ptrs[i]);
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    struct timespec start, end;

    clock_gettime(CLOCK_MONOTONIC, &start);

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, thread_func, NULL);
    }
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    clock_gettime(CLOCK_MONOTONIC, &end);
    double elapsed = (end.tv_sec - start.tv_sec)
                   + (end.tv_nsec - start.tv_nsec) / 1e9;
    printf("Elapsed: %.3f seconds\n", elapsed);
    return 0;
}

编译并对比测试

# 使用 glibc malloc
gcc -O2 -o bench_glibc malloc_bench.c -lpthread
./bench_glibc

# 使用 jemalloc(假设已安装)
gcc -O2 -o bench_jemalloc malloc_bench.c -lpthread -ljemalloc
./bench_jemalloc

# 或使用 LD_PRELOAD
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 ./bench_glibc

注意:在 NUMA 架构或线程数较多的服务器上,jemalloc 的优势更加明显。在单核或线程很少的场景下,差异可能不大。


1.3 主流内存分配器对比

一览表

特性ptmalloc (glibc)jemalloctcmallocmimalloc
开发者Wolfram GlogerJason EvansGoogleMicrosoft Research
多线程性能⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
内存碎片率
小对象性能一般优秀优秀极优
大对象性能一般良好良好良好
内存归还
Heap Profiling✅ 内置✅ 内置
运行时配置有限丰富有限有限
内存开销
代码规模~100K 行~100K 行~30K 行~10K 行
典型用户Linux 默认Redis, Rust, FacebookGoogle 内部.NET, Lean

详细对比

jemalloc vs ptmalloc (glibc)

  • jemalloc 使用多个 Arena + 线程本地缓存,大幅降低锁竞争
  • jemalloc 的大小类设计更精细,碎片率更低
  • jemalloc 支持显式的 malloc_stats_print() 和 heap profiling
  • ptmalloc 的优势在于零配置、零额外依赖

jemalloc vs tcmalloc

  • tcmalloc 采用 Thread-Local Storage + Central Free List + Page Heap 三层结构
  • tcmalloc 的线程缓存是固定大小的 per-thread freelist,jemalloc 则更灵活
  • jemalloc 在长时间运行的服务中碎片率通常更低
  • tcmalloc 在 Google 内部经过大规模验证,但社区版本更新较慢

jemalloc vs mimalloc

  • mimalloc 是微软研究院 2019 年推出的轻量级分配器
  • mimalloc 代码量极小(~10K 行),嵌入容易
  • mimalloc 在小对象分配上速度极快,甚至超过 jemalloc
  • jemalloc 在大内存场景、长时间运行服务中更稳定

1.4 设计目标

jemalloc 的设计围绕以下核心目标:

1. 可扩展性 (Scalability)

  • 通过 Arena 分区和线程本地缓存,让不同线程尽可能独立分配
  • 减少全局锁的使用,使性能随线程数线性增长

2. 低碎片率 (Low Fragmentation)

  • 精细的大小类设计(16B, 32B, 48B, 64B, …)减少内部碎片
  • Slab 分配器确保同大小的分配请求共用内存页
  • 积极的脏页 (dirty page) 回收机制减少外部碎片

3. 可观测性 (Observability)

  • 内置 heap profiling,支持按调用栈采样
  • malloc_stats_print() 输出详细的内存使用统计
  • 可通过 je_malloc_stats_print() 获取 arena、slab、page 等各层次信息

4. 可调优性 (Tunability)

  • 运行时通过 MALLOC_CONF 环境变量调整参数
  • 编译时通过 --with-xxx 选项裁剪功能
  • 支持自定义扩展 (Extent Hooks)

5. 可移植性 (Portability)

  • 支持 Linux、FreeBSD、macOS、Windows
  • 支持 x86_64、ARM、RISC-V 等多种架构
  • 支持 32 位和 64 位系统

1.5 适用场景

最适合的场景

场景原因
高并发服务Arena + TC 架构天生适合多线程
长时间运行的服务低碎片率保证内存稳定
内存敏感型应用精确的统计和 profiling 能力
Redis / MySQL 等数据库官方测试和社区验证的最优选择
容器化环境可通过 LD_PRELOAD 无侵入接入

可能不太适合的场景

场景说明
嵌入式系统jemalloc 的元数据开销相对较大
单线程短生命周期程序优势不明显,增加了依赖复杂度
对二进制大小极度敏感jemalloc 库约 1-2MB

1.6 谁在使用 jemalloc

┌─────────────────────────────────────────────────────┐
│                 jemalloc 用户生态                      │
├─────────────┬───────────────────────────────────────┤
│ 数据库       │ Redis, MariaDB, CockroachDB           │
│ 编程语言     │ Rust (alloc crate), Haskell (GHC)      │
│ 社交平台     │ Facebook (Meta), Instagram             │
│ 操作系统     │ FreeBSD (默认), Android (可选)          │
│ 中间件       │ Apache Kafka, Envoy Proxy              │
│ 游戏引擎     │ Unreal Engine (可选)                    │
└─────────────┴───────────────────────────────────────┘

1.7 本章小结

要点说明
jemalloc 是什么一款高性能通用内存分配器,由 FreeBSD 项目催生
核心优势多线程可扩展性、低碎片率、内置 profiling
主要对手tcmalloc (Google)、mimalloc (Microsoft)
最佳场景高并发服务、长时间运行进程、内存敏感型应用

扩展阅读

  1. 原始论文: Jason Evans, “A Scalable Concurrent malloc(3) Implementation for FreeBSD”, 2006
  2. jemalloc Wiki: https://github.com/jemalloc/jemalloc/wiki
  3. Glibc malloc 源码: https://sourceware.org/glibc/wiki/MallocInternals
  4. tcmalloc 论文: “TCMalloc : Thread-Caching Malloc”, Google, 2009
  5. mimalloc 论文: “mimalloc: Free List Sharding in Action”, Microsoft Research, 2019

下一章第 2 章:安装与编译 — 学习如何在各平台上安装和编译 jemalloc。