内存分配中的 Arena 概念
目录
什么是 Arena?
Arena(竞技场/分配区)是现代内存分配器(如 glibc malloc、jemalloc)中采用的一种核心架构,用于解决多线程环境下的内存分配性能问题。它的本质是将全局的堆内存分割成多个相对独立的区域,每个线程优先从自己的区域分配内存,从而减少锁竞争。
为什么需要 Arena?
单线程时代的问题
// 传统单堆内存管理
static char heap[HEAP_SIZE];
static char* current = heap;
void* malloc_single(size_t size) {
void* ptr = current;
current += size;
return ptr;
}
多线程时代的挑战
// 多线程竞争全局堆需要加锁
std::mutex heap_mutex;
void* heap_base;
void* malloc_global_lock(size_t size) {
std::lock_guard<std::mutex> lock(heap_mutex); // 性能瓶颈!
// ... 分配逻辑
return ptr;
}
glibc malloc 的 Arena 实现
1. Arena 数据结构
// glibc malloc 的 arena 结构(简化)
struct malloc_state {
mutex_t mutex; // 本arena的锁
mchunkptr top; // 指向top chunk
mchunkptr bins[NBINS * 2 - 2]; // 各种大小的bin链表
unsigned int binmap[BINMAPSIZE]; // bin位图
struct malloc_state* next; // 链表下一个arena
INTERNAL_SIZE_T system_mem; // 本arena从系统分配的内存总量
INTERNAL_SIZE_T max_system_mem;
};
2. Arena 分配策略
// glibc 的 arena 管理逻辑
static struct malloc_state main_arena; // 主arena
static struct malloc_state** free_list; // 空闲arena列表
// 线程获取arena的流程
struct malloc_state* arena_get(thread_id tid, size_t size) {
struct malloc_state* arena;
// 1. 首先尝试线程本地存储(TLS)中获取绑定的arena
arena = get_thread_arena();
if (arena != NULL && trylock_arena(arena)) {
return arena; // 成功获取自己的arena
}
// 2. 尝试从空闲arena列表获取
arena = get_free_arena();
if (arena != NULL) {
set_thread_arena(arena);
return arena;
}
// 3. 创建新的arena
if (arena_count < max_arenas) {
arena = create_new_arena();
set_thread_arena(arena);
return arena;
}
// 4. 所有方法失败,等待主arena
lock_arena(&main_arena);
return &main_arena;
}
3. Arena 数量限制
# glibc 的 arena 数量计算规则
number_of_arenas = min(
# 核心数的倍数
8 * number_of_cpu_cores,
# 或内存大小的函数
total_memory / (128 * 1024),
# 系统限制
MAX_ARENAS
)
jemalloc 的 Arena 改进
1. 更细粒度的 Arena 管理
// jemalloc 的 arena 结构
struct arena_s {
// 每个arena有自己的size classes
size_class_t size_classes[SC_NSIZES];
// 更精细的bin管理
bin_t bins[NBINS];
// 使用extent(范围)而不是chunk
extent_tree_t extents_dirty; // 脏extents
extent_tree_t extents_muzzy; // 模糊extents
extent_tree_t extents_retained; // 保留extents
};
2. Arena 分配算法对比
| 特性 | glibc malloc | jemalloc |
|---|---|---|
| Arena 数量 | 有限制(CPU核心数×8) | 可配置,通常更多 |
| 内存回收 | 不积极,RSS保持高位 | 积极,通过purge线程 |
| 碎片管理 | 简单的bin链表 | 基于extent的range管理 |
| 线程绑定 | 线程与arena绑定 | 更灵活的分配策略 |
Arena 的生命周期问题
glibc 的 Arena 内存滞留
void worker_thread() {
// 线程分配大量内存
void* buffer = malloc(1024 * 1024); // 1MB
// 处理工作...
process_data(buffer);
free(buffer); // 释放内存
// 但arena仍然保留这些内存,不立即归还系统
}
// 问题:即使线程结束,arena及其内存仍然存在
jemalloc 的解决方案
// jemalloc 的arena清理机制
void arena_decay(arena_t* arena) {
// 定期检查并清理空闲extents
if (should_purge(arena)) {
// 通过madvise释放物理内存
extent_purge(extent);
}
}
// 背景线程定期执行decay
void* jemalloc_bg_thread(void* arg) {
while (true) {
sleep(decay_interval);
for_each_arena(arena_decay);
}
}
实际影响示例
高并发场景下的内存使用
# 初始状态
PID RSS VSZ COMMAND
1234 100M 500M my_grpc_server
# 并发测试后 - glibc malloc
PID RSS VSZ COMMAND
1234 450M 500M my_grpc_server # RSS不下降!
# 并发测试后 - jemalloc
PID RSS VSZ COMMAND
1234 120M 500M my_grpc_server # RSS正常回收
代码层面的观察
#include <malloc.h>
#include <iostream>
void print_arena_info() {
struct mallinfo mi = mallinfo();
std::cout << "Arena total: " << mi.arena << " bytes\n";
std::cout << "Free chunks: " << mi.fordblks << " bytes\n";
std::cout << "MMAP regions: " << mi.hblks << "\n";
}
// 在glibc中,即使fordblks很大,RSS也可能不下降
总结
Arena 的核心价值:
- 减少锁竞争:多线程并行分配,提高并发性能
- 提高缓存局部性:线程使用自己的arena,缓存命中率更高
- 隔离内存区域:不同arena相对独立,减少干扰
关键区别:
- glibc:arena管理相对保守,内存回收不积极,导致RSS居高不下
- jemalloc:主动的内存回收和更精细的arena管理,内存使用更高效
这就是为什么在高并发gRPC服务中,切换到jemalloc能显著改善内存回收问题的根本原因。
Do not communicate by sharing memory; instead, share memory by communicating.

浙公网安备 33010602011771号