下面用生动形象的比喻,结合实际流程,帮你理解Android系统中Jemalloc的整体分配流程。
一、Jemalloc是什么?
Jemalloc是Android(尤其是高版本)默认的C/C++内存分配器,负责native层的malloc/free
等操作。它以高效、低碎片、支持多线程著称。
二、形象比喻:超级仓库的“分货员”团队
想象你有一个超级仓库,里面有很多不同大小的货架。每天有无数工人(线程)来申请存放或取走不同大小的包裹(内存块)。Jemalloc就是这个仓库的“分货员”团队,负责高效地分配和回收货架空间。
三、Jemalloc的分配流程(形象+技术细节)
1. 分货员分组:多级分区
- 小件专员(Tcache/Thread Cache):每个工人(线程)有自己的小货架,专门放小包裹(小内存块)。
- 中件专员(Bin/Small/Medium bins):仓库有专门区域,按包裹大小分不同货架(bin),比如8B、16B、32B、64B……一直到几KB。
- 大件专员(Large/Run/Chunk):大包裹直接分配大货架,甚至单独开辟一块区域。
2. 分配流程(以malloc为例)
步骤1:工人(线程)来申请包裹(内存)
- 比如:
malloc(24)
,要24字节。
步骤2:分货员先查“自己小货架”(Tcache)
- 先看自己手头有没有合适的空位(Tcache)。
- 有就直接给,速度极快(O(1))。
步骤3:没有就去“公共货架”(Bin)
- 没有合适的,就去仓库公共区找(bin)。
- Jemalloc把内存池分成很多“bin”,每种bin只放特定大小的包裹。
- 找到合适的bin,分配一个空位。
步骤4:还不够就“拆大货架”(Chunk/Run)
- 如果bin也没空位,就从更大的货架(chunk)里“切”一块下来,分成小块补充到bin里。
步骤5:超大包裹,单独开辟(Huge)
- 如果申请的包裹特别大(比如几MB),直接单独开辟一块新区域(mmap),不走普通货架。
步骤6:分配完毕,返回地址
- 分货员把包裹交给工人,工人拿着地址去用。
3. 回收流程(free)
- 工人用完包裹后,把它还给分货员。
- 分货员先放回自己的小货架(Tcache),腾不下了就还回公共货架(bin)。
- 超大包裹直接归还给仓库(munmap)。
4. 多线程高效:每人一套小货架
- 每个线程有自己的Tcache,互不干扰,减少锁竞争。
- 只有Tcache不够用时,才需要加锁访问公共区。
5. 防止碎片:定期整理货架
- Jemalloc会定期整理,把零散的小空位合并,腾出大块空间,防止“货架碎片”太多。
四、流程图(简化版)
malloc(size)
|
v
[线程Tcache] --有空位?--> [直接分配]
|否
v
[公共bin] --有空位?--> [分配]
|否
v
[切大块chunk/run] --分成小块--> [补充bin]
|
v
[超大包裹] --> [单独mmap]
五、总结口诀
“小件先找自家柜,大件分区再切配;超大单独开新仓,分货高效少浪费!”
六、实际开发中的好处
- 速度快:小内存分配几乎不加锁,极快。
- 低碎片:分区管理,定期整理。
- 多线程友好:每线程独立缓存,减少锁竞争。
- 大内存高效:大块直接mmap,避免影响小块分配。
我们继续深入,用形象+技术细节,进一步讲解Jemalloc的关键结构、源码要点、调优手段,以及如何在Android上分析Jemalloc的分配情况。
一、Jemalloc的关键结构(形象+源码)
1. Tcache(Thread Cache)——“每人一套小货架”
- 作用:每个线程有自己的小缓存,存放常用的小内存块。
- 源码结构:
struct tcache_s
- 好处:极大减少多线程下的锁竞争,提升分配/回收速度。
2. Arena(竞技场)——“分区大仓库”
- 作用:Jemalloc把整个内存池分成多个Arena,每个Arena可以服务一组线程。
- 源码结构:
struct arena_s
- 好处:多线程时,线程分散到不同Arena,进一步减少锁竞争。
3. Bin(分区货架)——“不同尺寸的货架”
- 作用:每个Arena里有很多Bin,每个Bin只放特定大小的内存块(如8B、16B、32B…)。
- 源码结构:
struct bin_s
- 好处:同尺寸内存块集中管理,分配/回收效率高。
4. Run/Chunk(大货架/大区域)
- 作用:Bin里的小块内存其实是从更大的Run/Chunk里切出来的。
- 源码结构:
struct arena_run_s
、struct chunk_s
- 好处:大块切小块,灵活高效,减少碎片。
5. Huge(超大包裹区)
- 作用:超大内存直接mmap,不走普通分区。
- 源码结构:
struct huge_s
- 好处:大内存独立管理,避免影响小块分配。
二、Jemalloc源码分配流程(伪代码)
void* malloc(size_t size) {
if (size <= TCACHE_MAX) {
// 1. 先查线程自己的Tcache
ptr = tcache_alloc(size);
if (ptr) return ptr;
}
// 2. 查找当前线程绑定的Arena
arena = choose_arena();
// 3. 在Arena的Bin里找合适的空位
ptr = arena_bin_alloc(arena, size);
if (ptr) return ptr;
// 4. Bin没空位,从Run/Chunk切新块
ptr = arena_run_alloc(arena, size);
if (ptr) return ptr;
// 5. 超大内存,直接mmap
ptr = huge_alloc(size);
return ptr;
}
三、Jemalloc调优手段(Android常用)
1. 环境变量调优
MALLOC_CONF
:可以设置Jemalloc参数,如narenas
(Arena数量)、lg_chunk
(Chunk大小)、tcache
(是否启用Tcache)等。- 例:
export MALLOC_CONF="narenas:8,lg_chunk:21,tcache:true"
- 例:
2. 分配策略调优
- 多线程场景:增加Arena数量,减少锁竞争。
- 大内存场景:调整Chunk大小,减少碎片。
- 频繁小分配:开启Tcache,提升性能。
3. 内存分析工具
- jemalloc自带的profiling(高版本支持):可输出分配/回收统计。
- Android自带工具:
dumpsys meminfo
、heapprofd
、perfetto
等。
四、如何在Android上分析Jemalloc分配情况
1. 查看内存分布
adb shell dumpsys meminfo <pid>
adb shell cat /proc/<pid>/maps
2. Jemalloc统计接口
- Android 8.0+,libc.so内置jemalloc,可以用
mallinfo
、malloc_info
等接口获取分配统计。 - 例:
#include <malloc.h> struct mallinfo mi = mallinfo(); printf("Total allocated: %d\n", mi.uordblks);
3. jemalloc profiling(高阶)
- 编译时开启profiling,设置
MALLOC_CONF="prof:true,prof_active:true"
,可生成heap profile。 - 用
jeprof
等工具分析。
4. heapprofd(Android 10+)
- 支持native heap采样分析,能看到jemalloc分配热点。
- 参考:Android官方heapprofd文档
五、常见问题与优化建议
- 碎片多:适当增大Chunk,定期整理(purge)。
- 多线程争抢:增加Arena数量,开启Tcache。
- 内存泄漏:用profiling工具定位分配热点和未释放内存。
- 大内存分配慢:合理设置Huge分配阈值。
六、形象总结口诀
“线程自带小货架,分区大仓多分家;大块单独开新仓,调优参数防卡壳!”