jemalloc 是一款第三方内存分配器,用来替代Linux系统自带的malloc/free,专门解决多线程下内存分配慢、内存碎片多的问题。
先搞懂:malloc是什么
C语言写程序,malloc()申请内存,free()释放内存。
char *p = malloc(1024); // 向内存分配器要1024字节内存
free(p); // 用完还给分配器
malloc不是系统调用!它只是用户态库函数 。 真正向操作系统拿大块内存靠 brk / mmap。
类比: 操作系统 = 大仓库,放着全部物理内存 malloc = 仓库管理员。程序找管理员要一小块内存;管理员不会每次都跑大仓库,自己手里缓存一大块,切小块分给程序。
Linux系统自带默认分配器叫 glibc malloc(ptmalloc2) ,平时写小程序用它没问题;但是高并发多线程程序,它会出两个大麻烦:
- 锁竞争严重:多线程同时malloc,要抢一把全局锁,大量线程排队等待,CPU上不去。
- 内存碎片:频繁小块申请释放,剩下很多零零碎碎的小空隙,明明空闲内存总和很大,却拿不出一块连续内存;看起来内存占用很高,实际很多是碎片。
碎片类比: 停车场,很多小车停了又开走,留下一堆1‑2个车位的空位。空位总数很多,但是你要停一辆大车,找不到一整块连续位置。这就是内存碎片。
jemalloc就是换一个更聪明的管理员。
jemalloc核心设计思路(小白版)
1. 每个线程有自己的缓存(tcache)
ptmalloc多线程抢同一把锁,排队堵车。
jemalloc:每个线程单独配一个本地小缓存 tcache
- 线程申请小块内存,优先从自己本地tcache里面拿,不需要抢锁,速度飞快。
- free释放小块内存,先还给自己线程的tcache,不还给全局。
- tcache满了,才把一批内存交还给全局;内存不够了再从全局拿一批。
👉 大大减少多线程锁冲突,高并发性能提升非常明显。
2. 分层内存管理:arena 竞技场
jemalloc把内存划分为多个独立区域,叫做 arena。
- 多个arena互相独立,各自管理自己的内存池,线程分配会绑定到不同arena,减少互相干扰。
- arena下面再分:huge(大内存)、large(大块)、small(小块)。 小块内存按固定规格对齐:8B、16B、32B ... 512B、1KB、2KB。 比如你malloc(100字节),实际给你128字节的块。用规格化块,分配释放简单,减少碎片。
3. 非常重视减少内存碎片
- 小块按规格归类管理;
- 会把连续空闲小块合并;
- 支持把长期闲置内存归还回操作系统 (真正释放,降低程序RSS内存占用),这个叫 purge(内存回退)。
ptmalloc有时候内存free之后,还留在进程手里不还给操作系统,程序RSS一直居高不下。jemalloc可以主动把空闲内存还给OS。
关键名词简单翻译
- tcache:线程本地缓存 每个线程私有,小块内存优先在这里分配,无锁,速度最快。
- arena:内存竞技场 多个独立内存池,降低多线程竞争。CPU核多就会生成多个arena。
- chunk:向操作系统mmap申请的一大块内存,是arena的原材料。
- extent:chunk里面划分出来的连续内存块。
- purge:把长时间不用的空闲内存还给操作系统,降低程序占用内存。
和系统glibc‑malloc简单对比
| 特性 | glibc malloc(ptmalloc2) | jemalloc |
|---|---|---|
| 多线程小块分配 | 全局锁,高并发抢锁卡顿 | 每个线程tcache本地缓存,几乎无锁 |
| 内存碎片 | 频繁小块业务碎片比较多 | 碎片控制更好 |
| 空闲内存还给OS | 不够积极,RSS容易虚高 | 支持主动purge归还内存 |
| 内存统计 | 功能弱 | 自带完善内存统计,可以看哪里内存泄露、哪里占用大 |
| 体积&开销 | 系统自带,开箱即用 | 需要链接库;有一点点额外内存开销 |
注意:jemalloc不是万能,单线程小程序,两者差距很小;只有多线程高并发场景优势才明显。
哪些软件在用jemalloc?
Redis、Nginx、RocksDB、LevelDB、很多自研KV数据库、ntyco协程项目、Mysql都经常替换成jemalloc。
为什么KV存储特别爱它:KV就是大量小块malloc/free,成千上万key‑value频繁创建销毁,最容易出现锁竞争+内存碎片。
怎么用上jemalloc(小白不用写代码改逻辑)
不需要改一行业务C代码! 编译时链接,或者用LD_PRELOAD动态劫持替换系统malloc函数。
# 运行程序前预加载jemalloc,直接替换malloc/free
LD_PRELOAD=libjemalloc.so ./your_kv_server
这就是你之前看过的 LD_PRELOAD 劫持!jemalloc最常见用法就是这样,原有代码完全不动,直接替换内存分配器。
jemalloc缺点(不是完美神器)
- 会有少量元数据开销:它自己要记录内存块信息,会多占一点点内存;
- tcache本地缓存的内存不会立刻释放,线程手里会留一部分空闲内存,短时间RSS看着偏高;长时间空闲会purge还给操作系统;
- 嵌入式资源极小的设备不适合;主要面向服务器。
容易踩坑点
- tcache缓存是线程私有的:线程退出时,它tcache里面残留的内存不会立刻归还,短期RSS降不下来;
- purge是有成本的,频繁purge会消耗CPU;jemalloc会有阈值控制,不会没事反复还给OS。
📌一句话总结
jemalloc是高性能内存分配器。靠线程本地缓存tcache + 多独立arena,大幅降低多线程malloc的锁竞争,同时更好控制内存碎片;很多存储、网络服务通过LD_PRELOAD直接替换系统malloc,业务代码一行不改就能提升性能。
和你之前学过的技术关联回顾
- fork:创建进程,内存管理交给分配器;
- ntyco协程 + io_uring做高性能KV;KV大量小块内存申请释放,搭配jemalloc就是经典组合;
- LD_PRELOAD:可以劫持malloc函数,无缝换成jemalloc。
参考链接 :0voice · GitHub