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

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

第 1 章:jemalloc 概述

1.1 什么是 jemalloc

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

发展历程

年份 里程碑
2005 Jason Evans 开始为 FreeBSD 开发 jemalloc
2006 发表论文 “A Scalable Concurrent malloc(3) Implementation for FreeBSD”
2007 成为 FreeBSD 7.0 的默认分配器
2009 Facebook 采用 jemalloc 作为其服务端默认分配器
2011 Redis 将 jemalloc 作为默认内存分配器
2015 Rust 选择 jemalloc 作为标准库默认分配器(后在 1.28 改为系统分配器)
2017 jemalloc 5.0 发布,大幅改进性能和配置接口
2022 jemalloc 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) jemalloc tcmalloc mimalloc
开发者 Wolfram Gloger Jason Evans Google Microsoft Research
多线程性能 ⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
内存碎片率
小对象性能 一般 优秀 优秀 极优
大对象性能 一般 良好 良好 良好
内存归还
Heap Profiling ✅ 内置 ✅ 内置
运行时配置 有限 丰富 有限 有限
内存开销
代码规模 ~100K 行 ~100K 行 ~30K 行 ~10K 行
典型用户 Linux 默认 Redis, Rust, Facebook Google 内部 .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。