您的位置 首页 golang

Golang内存分配

引言

golang是谷歌2009年发布的开源编程语言,截止目前go的release版本已经到了1.12,golang 语言专门针对多处理器系统应用程序的编程进行了优化,使用 golang 编译的程序可以媲美 C /C++代码的速度,而且更加安全、支持并行进程。和其他“高级语言”一样,golang同样有一套自己的内存管理机制,自主的去完成内存分配、垃圾回收、内存管理等过程,从而避免频繁的向操作系统申请、释放内存,有效的提升go语言的处理性能。由于篇幅有限,本文重点针对golang1.12.6版本就内存分配情况进行一下梳理和讲解。golang的内存管理是基于tcmalloc模型设计,但又有些差异,局部缓存并不是分配给进程或者 线程 ,而是分配给P(Processor);golang的GC是stop the world,并不是每个进程单独进行GC;golang语言对span的管理更有效率。

在进入正题之前,我们先回顾一下c语言内存是如何申请的,常用方式是调用malloc函数,指定要分配的大小,直接向操作系统申请,那我们来思考一下这种方式有没有什么问题?它会涉及到用户态和内核态的切换过程,那么频繁的进行用户态和内核态切换就会带来很大的耗时,导致性能下降,因此我们必须从语言层面找一种方式减少这么操作,那就是自己做一套内存管理机制。

就内存管理来说,如果要让我们去设计,思考一下都需要哪些功能模块才能保证高效稳定?

  • 内存池 :要减少用户态和内核态的频繁切换就需要自己申请一块内存空间,将之分割成大小规格不同的内存块来供程序使用,内存池是再适合不过的组成部分了。
  • GC:内存管理不光需要使用方便,还要保证内存使用过程能够节约,毕竟整个系统的内存资源是有限的,那么就需要GC进行动态的垃圾回收,销毁无用的对象,释放内存来保证整个程序乃至系统运行平稳。
  • 锁:一个应用程序内部之间存在大量的线程,线程之间资源是共享的,那么要保证同一块内存使用过程不出现复用或者污染,就必须保证同一时间只能有一个线程进行申请,第一个想到的肯定是锁,对公共区域的资源一定要加锁,另一种方式就是内存隔离,这个在golang的mcache中会有体现。

下面我们进入正题,基于上面分析的问题对golang进行一下研究,看看golang到底怎么管理内存的。

基本概念

1.什么是span

首先我们来介绍一下span的概念,span是golang内存管理的基本单位,每个span管理指定规格(以page为单位)的内存块,内存池分配出不同规格的内存块就是通过span体现出来的,应用程序创建对象就是通过找到对应规格的span来存储的,下面我们看一下mspan的结构。

go1.12.6src runtime mheap.go

Golang内存分配

Golang内存分配

根据源码和上图结合来看,会更加容易理解mspan,每一个mspan就是用来给程序分配对象空间的,也就是说一般我们对象都会放到mspan中管理,这里我们重点解释一下如图所示的几个属性,startAddr 是该mspan在 arena 区域的首地址,freeindex 用来表示下一个可能是空对象的位置,也就是说freeindex之前的元素(存储对象的空间)均是已经被使用的,freeindex之后的元素可能被使用可能没被使用,allocCache是从freeindex开始对后续元素分配情况进行缓存标记,通过freeindex和allocCache结合进行查找未分配的元素位置效率会更高,我们能快速的找到一个空对象分配给程序使用,而不用全局遍历。allocBits用来标识该span中所有元素的使用分配情况,gcmarkBits 用来sweep过程进行标记垃圾对象的,用于后续gc。

2.怎么区分span

那么要想区分不同规格的span,我们必须要有一个标识,每个span通过splanclass标识属于哪种规格的span,golang的span规格一共有67种,具体查看

go1.12.6srcruntimesizeclasses.go,可看到下图的规格表

Golang内存分配

其中:

  • class: 分类id或者规格id,也就是spanclass, 表示该span可存储的对象规格类型
  • bytes /obj:该列代表能存储每个对象的字节数,也就是说可以存储多大的对象,字段是elemsize
  • bytes/span:每个span占用堆的字节数,也即页数*页大小,npages*8KB
  • objects: 每个span可分配的元素个数,或者说可存储的对象个数,也就是nelems,也即(bytes/spans)/(bytes/obj)
  • tail bytes: 每个span产生的内存碎片,也即(bytes/span)%(bytes/obj)
  • max waste:最大浪费比例,(bytes/obj-最小使用量)*objects/(bytes/span)*100,比如classId=2 最小使用量是9bytes,则max waste=(16-9)*512/8192*100=43.75%

通过上表,我们可以很清楚的知道在创建一个对象时候,需要去选哪一个splanclass的span去获取内存空间,一个span能存多少这样大小的对象等等信息,非常清晰而又尽可能节约的去使用内存。另外上表可见最大的对象是32KB大小,超过32KB大小的由特殊的class表示,该class ID为0,每个class只包含一个对象。所以上面只有列出了1-66。

内存管理组件

阐述完一些基本概念,我们可以知道对象是存在span中,大家肯定会疑惑那span放在哪,怎么把这些各种规格孤立的span串起来?下面我们来说一下golang的内存管理组件,内存分配是由内存 分配器 完成,分配器由3种组件构成:mcache、mcentral、mheap,我们来详细讲一下每个组件。

我们知道golang之所有有很强的并发能力,依赖于它的G-P-M并发模型,

Golang内存分配

1.mcache

mcache就绑在并发模型的P上,也就是说我们每一个P都会有一个mcahe绑定,用来给协程分配对象存储空间的。下面具体看一下mcache的结构

go1.12.6srcruntimemcache.go

Golang内存分配

可以看到在mcache结构体中并没有锁存在,这是因为每个P都会绑定一个mcache,而每个P同时只会处理一个groutine,而且不同P之间是内存隔离的,因此不存在竞争情况。关键字段都已经在代码中解释了,这里我们重点关注一下 alloc [numSpanClasses]*mspan,由于SpanClasses一共有67种,为了满足指针对象和非指针对象,这里为每种规格的span同时准备scan和noscan两个,因此一共有134个mspan缓存 链表 ,分别用于存储指针对象和非指针对象,这样对非指针对象扫描的时候不需要继续扫描它是否引用其他对象,GC扫描对象的时候对于noscan的span可以不去查看bitmap区域来标记子对象, 这样可以大幅提升标记的效率。另外mcache在初始化时是没有任何mspan资源的,在使用过程中会动态地申请,不断的去填充 alloc[numSpanClasses]*mspan,通过双向链表连接,如下图所示:

Golang内存分配

通过图示我们可以看到alloc[numSpanClasses]*mspan管理了很多不同规格不同类型的span,golang对于[16B,32KB]的对象会使用这部分span进行内存分配,所以所有在这区间大小的对象都会从alloc这个数组里寻找,看下源码:

Golang内存分配

而对于更小的对象,我们叫它tiny对象,golang会通过tiny和tinyoffset组合寻找位置分配内存空间,这样可以更好的节约空间。源码如下:

Golang内存分配

2.mcentral

刚才我们提到mcache中的mspan都是动态申请的,那到底是去哪里申请呢?其实当空间不足的时候,mcache会去mcentral中申请对应规格的mspan,我们来继续看一下mcentral,先来看一下结构,go1.12.6srcruntimemcentral.go

Golang内存分配

看到mcentral的结构体会觉得很简单,首先与mcache有一个明显区别,就是有锁存在,由于mcentral是公共资源,会有多个mcache向它申请mspan,因此必须加锁,另外,mcentral与mcache不同,由于P绑定了很多Goroutine,在P上会处理不同大小的对象,mcache就需要包含各种规格的mspan,但mcentral不同,同一个mcentral只负责一种规格的mspan就够了,mcache就像一个市政府,mcentral就像国家部委,市政府需要管管辖区域内的所有方面的事情,而每个部委很专一,只管一方面,市政府需要哪方面资源,就去和对应部委对接就可以了。mcentral也是用spanclass 进行标记规格类型,该规格的所有未被使用的空闲mspan会挂载到nonempty 链表上,已经被mcache拿走,未归还的会挂载到empty 链表上,归还后会再挂载到nonempty上,用图表示如下,以规格sizeClass=1为例:

Golang内存分配

每一个mSpanList都挂着同一规格mspan双向链表,当然这个链表也不是固定大小的,都会动态变化的。

3.mheap

mcentral 的nonempty也有用完的时候,当nonempty为空,再被申请的时候,也就是mcentral空间不足了,那么它会向mheap申请新的页,下面我们看一下mheap结构。

go1.12.6srcruntimemheap.go

Golang内存分配

通个看这个结构,可以感觉到mheap相对复杂一些,重要字段我已经在代码中注释,我们知道每个golang程序启动时候会向操作系统申请一块虚拟内存空间,仅仅是虚拟内存空间,真正需要的时候才会发生缺页中断,向系统申请真正的物理空间,在golang1.11版本以后,申请的内存空间会放在一个heapArena数组里,由arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena表示,用于应用程序内存分配,下面展示一下数组中一块heapArena虚拟内存空间区域分配,

Golang内存分配

分为三个区域,分别是:

  • spans区域:存放span指针地址的地方,每个指针大小是8Byte
  • bitmap区域:用于标记arena区域中哪些地址保存了对象, 并且对象中哪些地址包含了指针,主要用于GC
  • arena区域:heap区域,程序内存分配的地方,管理的最小基本单位是页,golang一个page的大小是:8KB

下面看一下每个区域的大小情况

heapArena结构体如下:

Golang内存分配

关键字段计算定义如下:

Golang内存分配

通过源码可以看出spans大小等于arenaSize/8KB,可以理解为有多少page就准备出对应数量的“地址格子”,来充分保证能存下所有的span地址。

对于bitmap区域,由于bitmap是用来标记每个地址空间的使用情况,我们知道指针大小是8Byte,因此需要arenaSize/8个,一个bitmap可以标记四个地址,因此再除4。

Golang内存分配

如上图所示,是bitmap区域一个字节对arena区域的标记情况情况,高四位标记四个内存地址使用情况,低四位标记存储的是否是指针。对于arenaSize,根据源码公式,在64位非windows系统分配大小是64MB,windows 64位是4MB。

介绍完三个区域,我们再来看一下central [numSpanClasses],它就是管理的所有规格mcentral的集合,同样是134种,pad对齐填充用于确保 mcentrals 以 CacheLineSize 个字节数分隔,所以每一个 MCentral.lock 都可以获取自己的缓存行。而fixalloc类型的相关成员都是用来分配span、mache等对象的内存分配器,这里大家不要搞晕,具体来讲,以span举例,每一个span也需要空间存储,这个就是在spanalloc这个二叉树堆上存储,拿到这个对象,将startAddr 指向arena区域内的npages的内存空间才是给mcache使用的,或者说给P进行对象分配的。另外,由于mheap也是公共资源,一定也要有锁的存在。

下面结合图看一下:

Golang内存分配

从上图可以更清楚的看到,一个mheap会有134种mcentral,而每一种规格的mcentral会挂载该规格的mspan链表。

前面我们讲过tiny对象和小对象的内存分配,那大于 32KB 的对象怎么办呢?golang将大于32KB的对象定义为大对象,直接通过 mheap 分配。这些大对象的申请是以一个全局锁为代价的,所以同时只能服务一个P申请,大对象内存分配一定是页(8KB)的整数倍。结合源码再看一下:

Golang内存分配

可以看出不管多大对象,一切的空间都是从mheap获取的,那mheap要是不足了呢?就只能向操作系统申请了。

内存分配规则

讲完内存管理组件,我们再来总结一下内存分配规则:

  • tiny对象内存分配,直接向mcache的tiny对象分配器申请,如果空间不足,则向mcache的tinySpanClass规格的span链表申请,如果没有,则向mcentral申请对应规格mspan,依旧没有,则向mheap申请,最后都用光则向操作系统申请。
  • 小对象内存分配,先向本线程mcache申请,发现mspan没有空闲的空间,向mcentral申请对应规格的mspan,如果mcentral对应规格没有,向mheap申请对应页初始化新的mspan,如果也没有,则向操作系统申请,分配页。
  • 大对象内存分配,直接向mheap申请spanclass=0,如果没有则向操作系统申请。

流程图如下:

Golang内存分配

部分内存申请源码源码如下:

mcache向mcentral申请,调用go1.12.6srcruntimemcache.go refill方法

Golang内存分配

mcentral空间不足,向mheap申请分配页创建新的mspan,调用go1.12.6srcruntimemcental.go grow方法

Golang内存分配

mheap空间不足会调用go1.12.6srcruntimemheap.go grow方法进行系统申请

Golang内存分配

gc改进

通过上节流程图和代码,我们可以清晰的知道一个对象内存申请的整个过程,那思考一下这个流程是否完善,我们都知道golang通过gc进行垃圾回收,而完整的gc需要两次stop the world,如果我们完全依赖gc去垃圾回收是不是影响整个程序的性能,我们假设一个场景,mcentral的span一直不够用,那会不断的去向mheap去申请page空间,导致mheap的使用率很快就触发到gc的阈值,启动gc处理过程,频繁的gc就会导致频繁的程序停服,极大的会影响程序服务性能,那golang的做法是怎么样的呢?,在1.12版本里面golang对mheap结构添加了reclaimCredit 成员变量,每次mcentral向mheap申请新的page空间创建span的时候,都会先去扫描arenas里面的heapArena,去清理垃圾对象回收相同page数量的空间,由于扫描到的垃圾对象不可能正好等于相同page,多清理的page大小就会存到到reclaimCredit里面,下一次再扫描arenas的时候会先去抵消reclaimCredit,如果不够才会去扫描heapArena。通过这种方式有效的防止mheap使用率过快增长,下面是整个流程图:

Golang内存分配

同理,我们知道当mheap不够用的时候,会去向操作系统申请内存空间,如果增长过快,也会造成整个操作系统的不稳定,golang对这部分也做了处理,1.12版本mheap引入scavengeCredit 这个成员变量,当向操作系统申请内存空间的时候,会先去扫描free这个二叉树堆,span从大到小的扫描,释放所需大小的空间给os,多余释放的到小会存储到scavengeCredit中,下次再次扫描的时候会先扣除这个值。下面是整个流程图:

Golang内存分配

结尾

到此也就基本讲完了golang的内存分配的整个环节,本文也是受php内存管理启发,进行了一下golang源码深入研究,由于篇幅关系并没有把源码中的各种细节进行详细讲解,仅对整体流程进行梳理和阐述,对关键源码进行注释和解释,希望能给对golang感兴趣的伙伴给予一定帮助,如需更具体的了解,可以根据这个大流程进行源码学习。本文基于1.12.6版本源码一点点梳理,不足之处还请各位不吝雅正。

文章来源:智云一二三科技

文章标题:Golang内存分配

文章地址:https://www.zhihuclub.com/87148.shtml

关于作者: 智云科技

热门文章

评论已关闭

33条评论

  1. Dx 11 15 2018, IDC, Left, 1cm, Stage IA, Grade 2, 0 1 nodes, ER PR HER2 Dx DCIS, Left, Radiation Therapy Whole breast Hormonal Therapy Tamoxifen pills Nolvadex, Apo Tamox, Tamofen, Tamone Surgery Lumpectomy Left

  2. In addition, alcohol drinking is a potentially modifiable risk factor that can be targeted with preventive interventions at both the policy and the individual levels

  3. Two others, Sox17 A2 iCre 57 and FoxA2 2A iCre 58 have been reported to express Cre primarily in early endoderm, at a time when cardiac progenitors receive important developmental signals from this tissue 31 77; C by HER 2 in ER PR patients; proportion disease free and 95 confidence interval at 20, 60, and 100 months, respectively HER 2 negative 0

  4. DE Montano, WR Phillips Cancer screening by primary care physicians A comparison of rates obtained from physician self report, patient survey, and chart audit Am J Public Health 85 795 800, 1995 Crossref, Medline, Google Scholar 27

  5. viagra quitting finasteride hairlosstalk Obama and his aides have been trying to navigate a tricky path on Egypt, expressing displeasure with the army s actions while not entirely breaking a relationship crucial to U

  6. Because these P2Y 2 R mice lack the P2Y 2 R in both endothelial and SMCs, in the present study, we sought to clarify the role of endothelial P2Y 2 R in endothelium dependent relaxation

  7. com 20 E2 AD 90 20Viagra 20Natural 20Sandia 20 20Printable 20Viagra 20Coupons printable viagra coupons People familiar with the matter said the chief executives ofOrange, Telecom Italia, Telefonica and a senior official from Deutsche Telekom met Kroeslate last month to express their concerns about her plans Metastatic workup was negative and the patient underwent intensity modulated radiation therapy 4500 cGy with 5900 cGy boosts to the left groin

  8. At this visit, all of the necessary measurements and diagnostic tests that will be used for the surgery are performed

  9. Impact of Prior Unilateral Chest Wall Radiotherapy on Outcomes in Bilateral Breast Reconstruction

  10. Hello There. I discovered your weblog the use of msn. This is a very
    well written article. I’ll be sure to bookmark it and come back to read
    extra of your helpful information. Thank you for the post.
    I will certainly comeback.

  11. Waiting for referral to a surgeon With the use of BrdU to label dividing cells at a given time point, proliferation in the DG was significantly reduced in aged 21 mo rats compared with middle aged 6 mo rats

  12. The findings are published in the August issue of the journal Fertility and Sterility Tamoxifen appears to improve fertility in males with infertility due to an unknown reason

  13. An integrated MELD model including serum sodium and age improves the prediction of early mortality in patients with cirrhosis If you develop a fever a sign of infection, let your health care team know immediately so that you can get proper treatment

  14. Prostate cancer patients who receive surgery may experience erectile dysfunction inability to achieve or maintain an erection, difficulty climaxing, dry orgasm, and lowered libido

  15. I was looking for reassurance my decision was the right one, when I came across your article

  16. Coughing Having trouble breathing breathing makes crackling noises Mouth breathing with open Wheezing Weakness blue lips or a tongue Collapse Jugular vein that is enlarged quickly breathing

  17. For bone resorption, we assessed a second morning urine for urinary N telopeptide crosslinked collagen type 1 NTX, nmol bone collagen equivalents mmol creatinine; Osteomark, Ostex International, Seattle, WA

  18. For women using COCs that did not contain the progestins desogestrel DSG or gestodene GSD, VTE incidence was 8

  19. In both the patients in Schwartz et al s first report, the volume status was specifically looked at and was normal and the patients did not respond to volume, but to water restriction and ad libitum salt intake 1997 Jun; 15 6 2183 93

  20. Associations between the variables of interest and the IM change classified into three groups improved, no change, and worsened were statistically analyzed using one way analysis of variance ANOVA for continuous variables and linear by linear association for categorical variables

  21. Soft tissue infections caused by noncholera vibrios may present as one of two distinct clinical entities, primary vibrio cellulitis, or secondary cellulitis following primary bacteremia As far as we are aware, only one investigation has utilized BAL to examine the efficacy of Fur

  22. Immune cells as mediators of solid tumor metastasis There is, however, no clear evidence for this arbitrarily chosen lumen narrowing threshold, and for a stenosis of less than 50, hemodynamic abnormalities have been found that may also attain clinical significance for changes in renal function 63

网站地图