glibc内存管理——Linux内存管理小结二

【引言】

    最近在生产环境遇到一个奇怪的现象,nginx占用的虚拟内存和物理内存都很高,并且一直不会下降。

    因为服务器本身的业务量并不大,而且对比集群其他服务器nginx才几十兆的内存消耗,第一个想到的就是内存泄漏。但是连续观察了多天,内存也没有进一步上涨,和以前遇过的内存泄漏问题不是很像。

    偶然发现了一个特别有用的函数malloc_stats(),可以打印出进程malloc分配的虚拟内存信息。故gdb attach到其中一个worker进程,调用call malloc_stats()函数,将worker进程的内存分配信息打印出来,默认会输出到nginx的error.log中。 如下所示:

    从图中可以看到,虽然进程malloc了2G左右的内存,但是实际in use的只有28m,这说明其他绝大部分的内存都已经free了,出现了内存空洞,而不是内存泄漏。那到底内存空洞和内存泄漏有什么区别呢?

【glibc内存管理】

       Linux通过brk、mmap/munmap系统调用来操作内存。但是频繁的系统调用对于系统性能是很大的损耗。为了解决这个问题,glibc对系统调用进行了一层封装,相当于一个代理,它实现了一个内存池的功能,提供malloc/free函数给用户调用,也就是ptmalloc。类似有google实现的tcmalloc等。这样用户通过malloc/free函数来操作内存池,减少频繁系统调用带来的性能损耗。当内存池中的空闲内存可以满足用户申请时,优先返回内存池中的内存地址;否则glibc才会通过brk/mmap等系统调用去向系统申请。

【ptmalloc内存管理三个概念:arena、bin、chunk】

   1.  Arena:ptmalloc对进程内存是通过一个个Arena来进行管理的。

        在ptmalloc机制下,每个进程都有一个内存主分配区Main_arena和若干个非主分配区Non_Main_arena,主分配区只有一个,非主分配区可以动态增加。主分配区和非主分配区采用环形链表进行管理,每一个分配区采用互斥锁mutex实现多线程访问互斥。在多线程的场景下,如果线程申请内存时当前的分配区都已经被加锁,那么ptmalloc将会生成一个新的非主分配区。

        当一个线程调用malloc申请内存时,该线程先查看线程私有变量中是否已经存在一个分配区。如果存在,则对该分配区加锁,加锁成功的话就用该分配区进行内存分配;失败的话则搜索环形链表找一个未加锁的分配区。如果所有分配区都已经加锁,那么malloc会开辟一个新的分配区加入环形链表并加锁,用它来分配内存。释放操作同样需要获得锁才能进行。

        这种机制在多线程竞争锁激烈的场景下会带来一个问题:非主分配区开辟越来越多,因为它一旦开辟了就不会释放,一个分配区就是64MB。这样也会导致进程占用的内存越来越多(可能实际使用的并不多)。如果系统配置的ulimit进程最大虚拟内存值不是unlimited,那么当进程占用的内存达到ulimit值,就会core掉。这个情况也可以在pmap -p pid中看到里面有大量的64MB大小的anon内存块。这个问题可以通过设置MALLOC_ARENA_MAX环境变量来限制Arena的最大数量规避。

        主分配区可以使用brk和mmap向操作系统申请虚拟内存;但是非主分配区只能通过mmap申请,并且mmap每次申请的单位为64MB(64位系统下),再从中切割出用户所需大小的内存。

        主分配区使用brk调用可以访问进程的heap堆区。堆区的内存申请是通过brk调用将堆顶指针往高地址移动实现的,这样brk申请的内存肯定是连续的;释放的时候将堆顶指针往低地址移动(并不保证将free的内存归还给操作系统,需要堆顶出现一块连续的超过阈值大小的空闲内存时才会归还给操作系统)。如果主分配区使用mmap申请内存,那么free时会调用munmap直接将内存归还给操作系统。那么主分配区什么时候使用brk什么时候使用mmap呢?

        系统内核有一个阈值DEFAULT_MMAP_THRESHOLD,一般默认为128KB。当malloc申请的内存小于该阈值,glibc会采用brk去向系统申请内存;而申请的内存大于该阈值时,glibc会采用mmap去向系统申请。

        但是这样会带来一个问题:我们在程序中释放一个对象是无法保证它的内存是否连续释放的。可能出现先申请的内存,即堆底的内存先释放的情况,因为堆顶的内存还在使用,这时候是不能将堆顶指针往下移的,这时候虽然前面的那块内存已经free来,但是ptmalloc仍然不会将其还给操作系统,而是把它缓存到自己的池子里。这时候内存看上去仍然会被计算在进程的内存使用中,导致进程的内存使用量一直降不下去(如果堆顶的指针一直不释放的话),这即是内存空洞(内存碎片)。理论上这些内存空洞都是可以复用的,如果后面用户又申请同样大小的内存,ptmalloc会将这些空洞内存分配给它。所以在大量申请释放小块内存的场景下,进程容易出现内存空洞的问题。即随着某个时候业务量的激增,进程使用的虚拟内存涨上去了就降不下来了,看着就像是出现了内存泄漏。

        但是相比内存泄漏,内存空洞的问题情况相对要好一些,因为内存可以复用,如果后面的业务量不再继续上涨,理论上进程内存使用量是不会继续增多的。

    2. Chunk:ptmalloc使用chunk数据结构来表示一块具体申请或者释放的内存。

        也就是说chunk是glibc内存管理的“最小单位”。

    3. Bin:用于管理chunk的数据结构

        用户free掉的内存并不是都会马上归还给系统,ptmalloc会统一管理heap和mmap映射区域中的空闲的chunk,当用户进行下一次分配请求时,ptmalloc会首先试图在空闲的chunk中挑选一块给用户,这样就避免了频繁的系统调用,降低了内存分配的开销。ptmalloc将相似大小的chunk用双向链表链接起来,这样的一个链表被称为一个bin。

         关于chunk的数据结构细节不进行过多描述,详情可以参考华庭大神的文章:《glibc内存管理》

         https://paper.seebug.org/papers/Archive/refs/heap/glibc内存管理ptmalloc源代码分析.pdf

bin和chunk的示意图

【内存空洞和内存泄漏】

       简单来说,内存空洞是指进程已经free了内存,但是由于glibc的原因,这部分内存并没有还给操作系统,而是缓存在glibc为进程维护的内存池中。所以在top等工具中看起来这部分内存仍然是进程在使用。而这些内存空洞是可以被进程自身复用的,后续如果有同样大小的malloc请求,glibc会使用这部分空洞的内存进行分配。

        而内存泄漏,是进程调用了malloc申请内存,但是没有调用free释放。这样导致进程的内存空间一直上涨,后续的malloc请求无法复用前面申请的内存,直到达到ulimit的限制或者触发OOM。

        注意这里说的内存空间申请、释放和上涨都是针对虚拟内存空间来说。只有当申请的虚拟内存空间得到访问,比如对malloc的空间进行初始化,这时候才会将虚拟内存空间映射到物理内存空间。如果物理内存空间出现不足,而后续又有虚拟内存要映射过来,就会出现swap交换,将物理内存中暂时没有用到的数据置换到硬盘上配置的swap空间中。如果连swap空间也不足了,就会触发OOM,甚至系统hang死。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容