从《更好的内存管理-jemalloc》这边文章中,可以比较直接看到,在Jemalloc中,几个重要对象的关系,arena/bin/run之间关系。但是他没有揭示更深刻的东西。我们知道,arena包含多个bin,而bin也包含多个run。对于任何一个请求,根据size计算出,所在bin数组的下表。然后从这个bin中,提取合适的run来分配内存。这个大方向没有错,但如何实现就很讲究,下面我们来更深入的分析。
一、run的结构
header + bitmap + region0 + ...+ regionx
这个内存布局是动态的,根据arena_bin_info来确定的。
二、bin的结构
struct arena_bin_s { malloc_mutex_t lock; arena_run_t *runcur; arena_run_tree_t runs; };lock和runcur就没有什么好说的,最好玩的就是runs,后面再分析。
三、arena的结构
struct arena_s { malloc_mutex_t lock; arena_chunk_tree_t chunks_dirty; arena_chunk_t *spare; arena_avail_tree_t runs_avail; arena_bin_t bins[NBINS]; };
这里做了些裁剪,没有全部列出。到此为止,《更好的内存管理-jemalloc 》都介绍过了。我们来看更精彩的内容,我在上面的free过程分析中,一直强调chunk的分配方式是jemalloc的一个非常重要的基础,他的要点就是内存地址对齐,简单说,就是对于一个地址addr,通过CHUNK_ADDR2BASE宏就能直接得到所在的chunk。
下面,我们假设一个应用场景,我要分配一个大小为SIZE内存块,那么流程就是这样:
1、选定一个arena或者tcache,没啥好说的。
2、计算对应的对齐长度,选定arena中bins的下表。
3、从runcur或者runs选择一个run
4、从选定的run中,计算bitmap,得到空闲的region,后返回。
在正常情况下,这是个很完美的流程,没啥好说。但是出现没有足够内存情况下,问题就来了。需要从系统拉出一个新的Chunk来。让我们继续研究下chunk的结构,
struct arena_chunk_s { arena_t *arena; rb_node(arena_chunk_t) dirty_link; size_t ndirty; size_t nruns_avail; size_t nruns_adjac; arena_chunk_map_t map[1]; };在这个结构中,map的结构是最精彩的。首先map是个数组,他的下标对应于run在这个chunk中的位置,从代码
static arena_run_t * arena_bin_runs_first(arena_bin_t *bin) { arena_chunk_map_t *mapelm = arena_run_tree_first(&bin->runs); if (mapelm != NULL) { arena_chunk_t *chunk; size_t pageind; arena_run_t *run; chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(mapelm); pageind = ((((uintptr_t)mapelm - (uintptr_t)chunk->map) / sizeof(arena_chunk_map_t))) + map_bias; run = (arena_run_t *)((uintptr_t)chunk + (uintptr_t)((pageind - arena_mapbits_small_runind_get(chunk, pageind)) << LG_PAGE)); return (run); } return (NULL); }
解析到这里,我们可以得到几个重要结论。
其一、jemalloc的一个重要基石,就是CHUNK_ADDR2BASE,省了很多查找。
其二、chunk是从系统分配内存的的最小单位,但他本身包含了描述信息。
其三、run是内存分配的单元,其本身也包含了描述信息。
在jemalloc中,描述和数据的混合是一个重要特点。也是一个无奈之举,可以说是CHUNK_ADDR2BASE这把双刃剑的结果。