Go语言内存模型

目录

1,局部变量太多的问题:

2,栈桢太多问题

分段栈:

连续栈

堆内存

对象分配

mcache缓存位图

mcentral遍历span

mheap缓存查找

总结


栈内存(协程栈,调用栈)

GO的协程栈位于GO的堆内存上。

GO的堆内存在操作系统的虚拟内存上。

协程栈的作用:

1 协程执行路径。2 局部变量。 3 函数传参。4 函数返回值。

Go语言内存模型_第1张图片

sum函数执行完后,返回,执行print。这是开辟print的栈。

协程栈不够用怎么办?

1,局部变量太多,2 栈桢太多。

1,局部变量太多的问题:

逃逸分析:原因:1,不是所有变量都能放在协程栈上 2,栈桢回收后,继续使用原来的变量。3,太大的变量。

触发情形:1, 指针逃逸,2 空接口逃逸。3 大变量逃逸。

指针逃逸: 函数返回对象的指针,不能放在栈上,不然a函数结束后栈回收v就没了。

Go语言内存模型_第2张图片

空接口逃逸 1,函数的参数为interface{} 例如println函数,则很有可能逃逸,因为interface{}类型的函数经常需要放射,反射要求对象在堆上。

Go语言内存模型_第3张图片

大变量逃逸: 过大变量导致栈空间不足。

2,栈桢太多问题

扩容,1. 1.13前使用分段栈, 2 连续栈

分段栈:

Go语言内存模型_第4张图片

连续栈

Go语言内存模型_第5张图片

堆内存

。Go每次申请虚拟内存单元是64MB。最多2的20次方个虚拟单元。内存单元也叫heapArena。所有的heapArena组成了mheap(GO堆内存)。heapArena实际上就是去申请操作系统虚拟内存的一个结构。

Go语言内存模型_第6张图片

Go语言内存模型_第7张图片

Go语言内存模型_第8张图片

Go语言内存模型_第9张图片

GO 对分级分配进化,使用mspan。mspan实际上是heapArena被切成的一个个小块。

Go语言内存模型_第10张图片

解决如何寻找合适的mspan于是升级如下图:mcental实际上是span的链表头。其中有scan是需要GC扫描的,noscan是不需要GC扫描。所以实际上最上面这一大块是一个索引。

Go语言内存模型_第11张图片

mcental是中心索引,互斥锁保护。所以参考了GMP模型,增强线程本地缓存。所以线程mcache,每一个P都用一个mcache。mcache记录了分配给各个P的本地mspan。

Go语言内存模型_第12张图片

对象分配

微对象分配:从mcache拿到2级mspan;将多个微对象合并成一个16B存入。

macache每个级别的mspan只有一个,当mpan满了后会从mcentral 中换一个新的。mcentral中只有有限的mspan,当mspan缺少时,会从heapArena开辟新的mspan。

大对象分配:从heapArena开辟0级mspan。0级的mspan为大对象定制。

Go语言内存模型_第13张图片



mcache缓存位图

查找空闲元素空间时,需要从mcache中找到对应级别的mspan,mspan中拥有allocCache字段,其作为一个位图用于标记span中的元素是否被分配。 

mcentral遍历span

在mcentral查找时,需要遍历两个链表mcentral scan和mcentral nonscan。这是因为可能有些span虽然被垃圾回收器标记为空闲了,但还没来得及清理,这些span在清理后还可以使用。

mheap缓存查找

若在mcentral找不到可以用的span则需要到mheap中查找。mheap会首先查找每个逻辑处理器P中pageCache字段的cache。cache位图标记每个page是否被使用。

如果要分配的page过大或P的cache中找不到可用的page。就需要对mheap加锁,查找mheap管理的虚拟地址空间查看是否有可用的page, 这涉及GO语言对线性地址空间的位图管理。管理线性地址空间的位图结构叫做基数树。

Go语言内存模型_第14张图片这个树中每个节点都对应一个pallocSum,除了叶子节点之外,都包含连续8个字节点的内存信息。pallocSum由三部分组成,Start表示开头有多少连续内存页,Max表示最多有多少连续内存页,end表示有多少连续页。Go语言内存模型_第15张图片

并且我们每次查找不需要从根节点开始查找,我们有一个特别的字段searchAddr,只需要向searchAddr的地址后查找即可跳过已经查找的节点,减少查找时间。

Go语言内存模型_第16张图片

当当前chunk没有连续的page时。需要从基数树从上到下进行查找。可以先计算pallocSum开头有多少连续空间,若不足则计算最大多少连续空间。若max>申请的空间,则表示有充足的地址,则需要到下一级查找具体的位置。如果max< 申请的空间,我们可以将两个节点组合起来一个更大的空间。

如果基数树没有充足的空间,则需要向操作系统申请内存。

GO语言mheap结构:

Go语言内存模型_第17张图片

堆区的线性内存

  • spans 区域存储了指向内存管理单元 runtime.mspan 的指针,每个内存单元会管理几页的内存空间,每页大小为 8KB;
  • bitmap 用于标识 arena 区域中的那些地址保存了对象,位图中的每个字节都会表示堆区中的 32 字节是否空闲;
  • arena 区域是真正的堆区,运行时会将 8KB 看做一页,这些内存页中存储了所有在堆上初始化的对象;

 

对于任意一个地址,我们都可以根据 arena 的基地址计算该地址所在的页数并通过 spans 数组获得管理该片内存的管理单元 runtime.mspan,spans 数组中多个连续的位置可能对应同一个 runtime.mspan 结构。

Go 语言在垃圾回收时会根据指针的地址判断对象是否在堆中,并通过上一段中介绍的过程找到管理该对象的 runtime.mspan。这些都建立在堆区的内存是连续的这一假设上。这种设计虽然简单并且方便,但是在 C 和 Go 混合使用时会导致程序崩溃:

  1. 分配的内存地址会发生冲突,导致堆的初始化和扩容失败3;
  2. 没有被预留的大块内存可能会被分配给 C 语言的二进制,导致扩容后的堆不连续4

总结

Go语言内存模型_第18张图片

 

小对象分配路线:mcache->mcentral->mheap位图查找->mheap基数树查找->操作系统分配

     

你可能感兴趣的:(Go语言,golang,开发语言,后端)