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 发布,持续优化 |
核心特性
- Arena 分区管理:将内存划分为多个 Arena,减少线程间锁竞争
- Thread-Local Cache (TC):每个线程拥有本地缓存,快速分配小对象
- 大小类 (Size Class):按固定大小类别管理内存,减少碎片
- Slab 分配:将页 (Page) 切分为等大小的块 (Run),高效管理小对象
- Heap Profiling:内置内存分析工具,支持采样和泄漏检测
- 运行时可调:通过环境变量或配置文件在运行时调整参数
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 | 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) |
| 最佳场景 | 高并发服务、长时间运行进程、内存敏感型应用 |
扩展阅读
- 原始论文: Jason Evans, “A Scalable Concurrent malloc(3) Implementation for FreeBSD”, 2006
- jemalloc Wiki: https://github.com/jemalloc/jemalloc/wiki
- Glibc malloc 源码: https://sourceware.org/glibc/wiki/MallocInternals
- tcmalloc 论文: “TCMalloc : Thread-Caching Malloc”, Google, 2009
- mimalloc 论文: “mimalloc: Free List Sharding in Action”, Microsoft Research, 2019
下一章:第 2 章:安装与编译 — 学习如何在各平台上安装和编译 jemalloc。