Go内存分配机制-TCMalloc

本文是《循序渐进go语言》第四篇-Go内存分配机制。
      golang的内存采用了TCMalloc 这种分配机制。go是在多种语言诞生N多年之后,才由几个大神开发的。每种语言都有自己的内存分配机制,那么作为后起之秀,go为什么选用TCMalloc呢?因为TCMalloc(Thread-Cache Malloc) 在内存分配上性能做的更好,而且对内存的利用率也有所提高。先简单说一下TCMalloc在这两方面是如何做到的?

  • 性能提升主要从两点来看,第一点线程内部小对象的使用不存在锁竞争,减少了竞争带来的性能开销; 第二点内存大对象的分配直接在堆上,并且采用了自旋锁,某个线程等待另一个线程释放锁的时候,不会像传统互斥锁一样由运行态转到休眠态,等待线程一会处于忙等待,减少了线程状态的切换。
  • 在内存利用率方面,区分了线程、central、heap三级,线程之间有一个共享内存池(central 区)。当线程内部内存不足时,会向central申请,当不再使用时,归还到central区。每个线程都可以向central区申请和归还,充分利用了内存。central区内存不足时,跟heap申请,空闲时归还给heap。

      说了分配性能跟内存利用率方面的优势,有没有对TCMalloc产生浓浓的兴趣?
      本文将从如下几个方面,将我整理的知识点梳理一下:

  • TCMalloc内存块以及管理组件
  • go初始化时,在内存这部分做了什么?
  • TCMalloc 分配过程
  • TCMalloc 回收过程
  • TCMalloc 释放

1 TCMalloc内存块以及管理组件

1.1 内存块

go中的有两种内存块:span和object。span面向内部管理(可以是一种按照大小将内存页进行组织的形式),object面向内存分配。

  • span:由多个地址连续的页组成的大块内存
  • object: 将span按照特定大小切分成多个小块,每个小块可存储一个对象。

分配对象时,大的对象直接从堆上分配,小的对象从 Span 中分配。


小对象分配.png

span跟page的关系:


page-span.png

看一下span的定义哈:既包含起始页跟页数,又有object链表

type mspan struct {
      next *mspan //双向链表
      prev *mspan
      start pageID //起始页号
      npages uintptr //页数
      freelist gclinkptr //待分配的object 链表。
}

1.2 管理组件

tcmalloc 分配器有三种管理组件:

  • cache: 每个运行期工作线程都会有一个cache
  • central:为所有cache提供切分好的后备span资源
  • heap:管理闲置span,需要时向操作系统申请新内存

heap中包含一系列不同sizeclass的central,每个central中包含了对应大小的span列表,cache中 以sizeclass为索引管理多个用于分配的span。


image.png

2 go初始化时,在内存这部分做了什么?

在初始化阶段,预留了一大段虚拟地址空间,分了三部分:

首先是用户内存分配区域,其大小决定了可分配用户内存的上限
其次有一个位图bitmap,其为每个对象提供4bit标记位,用于保留指针、GC标记等信息
最后还有一个页所属span指针数组。

go 内存初始化做的工作主要有初始化上面的结构,然后为他们保留地址空间,然后初始化heap的一些其他属性。在初始化heap的时候,创建了多个不同大小的central。

3 TCMalloc 分配过程

3.1 TCMalloc分配

过程不复杂,我们画个流程图。


分配.png

分析了整体的线程内存分配流程,那么当资源不足时如何扩张呢?
资源不足分两种,第一种是central不足,另一种是heap不足。下面我们从分配的角度,看一下这两种的流程是什么样子的?

3.2 central的分配

central分配.png

其中维持noneempty跟empty链表过程中涉及到了sweepgen,这个用于central 中span的清理。
从central 里获取span时,优先取用已有资源。哪怕是要执行清理操作,只有当现有资源不满足时才会去heap中获取span。

3.3 heap的分配

heap自己维护了两个链表,busy跟busylarge,顾名思义,就是按照大小区分的。
在分配时,当是小对象时,放在busy链表供central使用,大对象放在busylarge,放在busylarge链表。

  • 为了避免浪费,当返回更大的span时,会将多余部分切出来重新放回heap链表。
  • 同时,还会尝试合并相邻限制的span空间,减少碎片。

当heap中的内存不足时,就只能向操作系统申请了。

3.4 向操作系统申请分配

使用了mmap,从指定位置申请内存。需要同步扩张heap的 bitmap和spans区域,以及调整arena_used这个位置指示器。

3.5 总结一下分配过程
  • 1 计算待分配对象对应的size class
  • 2 从cache.alloc 找到对应规格相同的span
  • 3 从span.freelist链表提取可用的object,如果span.freelist没有span可用,执行4
  • 4 从central 获取新的span, 如果有,分配给cache使用;如果central也没有可用span,执行5
  • 5 从heap获取span。如果heap有,则将span切分成对应大小的object,并将整个span给到central;如果heap也不足,则执行6
  • 6 向操作系统申请。

4 TCMalloc 回收过程

回收.png

5 TCMalloc 释放

main启动时会启动一个监控任务sysmon,每隔一段时间就会检查heap中的闲置内存块,如果闲置时间超过阈值,则释放其关联的物理内存。

6 参考文献

1、官方文档
2、自旋锁
3、《go语言学习笔记》
4、图解TCMalloc

7 其他

本文是《循序渐进go语言》的第四篇-《Go内存分配机制-TCMalloc》。
如果有疑问,可以直接留言,也可以关注公众号 “链人成长chainerup” 提问留言,或者加入知识星球“链人成长” 与我深度链接~

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

推荐阅读更多精彩内容

  • GO语言内存管理子系统主要由两部分组成:内存分配器和垃圾回收器(gc)。内存分配器主要解决小对象的分配管理和多线程...
    adrian920阅读 11,065评论 4 6
  • 1、动机 tcmalloc要比glibc2.3 malloc(基于ptmalloc2实现)要快,ptmalloc2...
    异客z阅读 5,562评论 0 1
  • 前言 编写过C语言程序的肯定知道通过malloc()方法动态申请内存,其中内存分配器使用的是glibc提供的ptm...
    杰克慢阅读 1,485评论 0 1
  • Go语言——内存管理 参考: 图解 TCMalloc Golang 内存管理 Go 内存管理 问题 内存碎片:避免...
    陈先生_9e91阅读 4,736评论 1 10
  • Go语言内置运行时(就是runtime),抛弃了传统的内存分配方式,改为自主管理。这样可以自主地实现更好的内存使用...
    ddu_sw阅读 1,340评论 0 5