Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1108858
  • 博文数量: 143
  • 博客积分: 969
  • 博客等级: 准尉
  • 技术积分: 1765
  • 用 户 组: 普通用户
  • 注册时间: 2011-07-30 12:09
文章分类

全部博文(143)

文章存档

2023年(4)

2021年(2)

2020年(4)

2019年(4)

2018年(33)

2017年(6)

2016年(13)

2014年(7)

2013年(23)

2012年(33)

2011年(14)

我的朋友

分类: 其他平台

2013-05-11 17:32:20

最近准备把jemalloc用到JVM中,就研究了一下它的源码,网上没有找到太多详细的资料,不像glibc的内存管理模块ptmalloc华庭写过一个很详细的文档

       首先介绍一下jemalloc中的几个核心概念:

更好的内存管理-jemalloc - Alex - wangkaisino的博客

       1.arena。jemalloc的核心分配管理区域,对于多核系统,会默认分配4*cores的Arena,线程采取轮询的方式来选择相应的arena来进行内存分配。

       2.chunk。具体进行内存分配的区域,目前的默认大小是4M。chunk以page(默认为4K)为单位进行管理,每个chunk的前几个page(默认是6个)用于存储后面所有page的状态,比如是否待分配还是已经分配;而后面的所有page则用于进行实际的分配。

       3.bin。用来管理各个不同大小单元的分配,比如最小的Bin管理的是8字节的分配,每个Bin管理的大小都不一样,依次递增。jemalloc的bin和ptmalloc的bin的作用类似。

       4.run。每个bin在实际上是通过对它对应的正在运行的Run进行操作来进行分配的,一个run实际上就是chunk里的一块区域,大小是page的整数倍,具体由实际的bin来决定,比如8字节的bin对应的run就只有1个page,可以从里面选取一个8字节的块进行分配。在run的最开头会存储着这个run的信息,比如还有多少个块可供分配。

       5.tcache。线程对应的私有缓存空间,默认是使用的。因此在分配内存时首先从tcache中找,miss的情况下才会进入一般的分配流程。


       接下来介绍它们之间的关系。每个arena有一个bin数组,根据机器配置不同它的具体结构也不同,由相应的size_class.h中的宏定义决定,在笔者的机器上这个bin数组会管理从8字节一直到3584字节共28种大小的bin,而每个bin会通过它对应的正在运行的run进行操作来进行分配的。每个tcahe有一个对应的arena,它本身也有一个bin数组(称为tbin),前面的部分与arena的bin数组是对应的,但它长度更大一些,因为它会缓存一些更大的块;而且它也没有对应的run的概念,因为它只做缓存,rnny它只有一个avail数组来存储被缓存的空间的地址。像笔者机器上的tcahe在arena最大的3584字节的bin的基础上,后面还有8个bin,分别对应4K,8K,12K一直到32K。

       这里想重点介绍一下chunk与run的关系。之前提到chunk默认是4M,而run是在chunk中进行实际分配的操作对象,每次有新的分配请求时一旦tcache无法满足要求,就要通过run进行操作,如果没有对应的run存在就要新建一个,哪怕只分配一个块,比如只申请一个8字节的块,也会生成一个大小为一个page(默认4K)的run;再申请一个16字节的块,又会生成一个大小为4096字节的run。run的具体大小由它对应的bin决定,但一定是page的整数倍。因此实际上每个chunk就被分成了一个个的run。


       可能说到这里读者还是十分模糊,那接下来就介绍一下jemalloc具体是如何进行内存分配的,具体流程如下:

       1.   如果请求size不大于arena的最小的bin(笔者机器上是3584字节),那么就通过线程对应的tcache来进行分配。首先确定size的大小属于哪一个tbin,比如2字节的size就属于最小的8字节的tbin,然后查找tbin中有没有缓存的空间,如果有就进行分配,没有则为这个tbin对应的arena的bin分配一个run,然后把这个run里面的部分块的地址依次赋给tcache的对应的bin的avail数组,相当于缓存了一部分的8字节的块,最后从这个availl数组中选取一个地址进行分配;
       2.   如果请求size大于arena的最小的bin,同时不大于tcache能缓存的最大块(笔者机器上是32K),也会通过线程对应的tcache来进行分配,但方式不同。首先看tcache对应的tbin里有没有缓存块,如果有就分配,没有就从chunk里直接找一块相应的page整数倍大小的空间进行分配(当这块空间后续释放时,这会进入相应的tcache对应的tbin里);
       3.   如果请求size大于tcache能缓存的最大块,同时不大于chunk大小(默认是4M),具体分配和第2类请求相同,区别只是没有使用tcache;
       4.   如果请求大于chunk大小,直接通过mmap进行分配。

       回收流程大体和分配流程类似,有tcache机制的会将回收的块进行缓存,没有tcache机制的直接回收(不大于chunk的将对应的page状态进行修改,回收对应的run;大于chunk的直接munmap)。需要关注的是jemalloc何时会将内存还给操作系统,因为ptmalloc中存在因为使用top_chunk机制(详见华庭的文章)而使得内存无法还给操作系统的问题。目前看来,除了大内存直接munmap,jemalloc还有两种机制可以释放内存:
       1.   当释放时发现某个chunk的所有内存都已经为脏(即分配后又回收)就把整个chunk释放;
       2.   当arena中的page分配情况满足一个阈值时对dirty page进行purge(通过调用madvise来进行)。这个阈值的具体含义是该arena中的dirty page大小已经达到一个chunk的大小且占到了active page的1/opt_lg_dirty_mult(默认为1/32)。active page的意思是已经正在使用中的run的page,而dirty page就是其中已经分配后又回收的page。
       上述两种机制保证了jemalloc不会出现类似ptmalloc中的内存无法交还给操作系统的问题。
   
       总体而言,jemalloc的设计思想比ptmalloc要简单,却也更先进,tcache的使用保证了分配效率,回收机制也保证了不会出现大量内存无法交还给操作系统的问题,同时将一些管理状态信息单独于分配的空间集中高效存储(大量使用bitmap)提高了内存的使用率。当然由于jemalloc中内存的使用都以page为单位,因此会有一些内部碎片的问题(比如申请5K的空间实际会使用8K的空间),但总体上它仍比ptmalloc占用的内存更少,效率更高。目前redis已经默认使用jemalloc,淘宝的Tengine也已经提供了jemalloc的支持。

    

参考资料:

1. jemalloc 3.0.0源码

2. 更好的内存管理-jemalloc 

阅读(7308) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~