目录
1,局部变量太多的问题:
2,栈桢太多问题
分段栈:
连续栈
堆内存
对象分配
mcache缓存位图
mcentral遍历span
mheap缓存查找
总结
栈内存(协程栈,调用栈)
GO的协程栈位于GO的堆内存上。
GO的堆内存在操作系统的虚拟内存上。
协程栈的作用:
1 协程执行路径。2 局部变量。 3 函数传参。4 函数返回值。
sum函数执行完后,返回,执行print。这是开辟print的栈。
协程栈不够用怎么办?
1,局部变量太多,2 栈桢太多。
逃逸分析:原因:1,不是所有变量都能放在协程栈上 2,栈桢回收后,继续使用原来的变量。3,太大的变量。
触发情形:1, 指针逃逸,2 空接口逃逸。3 大变量逃逸。
指针逃逸: 函数返回对象的指针,不能放在栈上,不然a函数结束后栈回收v就没了。
空接口逃逸 1,函数的参数为interface{} 例如println函数,则很有可能逃逸,因为interface{}类型的函数经常需要放射,反射要求对象在堆上。
大变量逃逸: 过大变量导致栈空间不足。
扩容,1. 1.13前使用分段栈, 2 连续栈
。Go每次申请虚拟内存单元是64MB。最多2的20次方个虚拟单元。内存单元也叫heapArena。所有的heapArena组成了mheap(GO堆内存)。heapArena实际上就是去申请操作系统虚拟内存的一个结构。
GO 对分级分配进化,使用mspan。mspan实际上是heapArena被切成的一个个小块。
解决如何寻找合适的mspan于是升级如下图:mcental实际上是span的链表头。其中有scan是需要GC扫描的,noscan是不需要GC扫描。所以实际上最上面这一大块是一个索引。
mcental是中心索引,互斥锁保护。所以参考了GMP模型,增强线程本地缓存。所以线程mcache,每一个P都用一个mcache。mcache记录了分配给各个P的本地mspan。
微对象分配:从mcache拿到2级mspan;将多个微对象合并成一个16B存入。
macache每个级别的mspan只有一个,当mpan满了后会从mcentral 中换一个新的。mcentral中只有有限的mspan,当mspan缺少时,会从heapArena开辟新的mspan。
大对象分配:从heapArena开辟0级mspan。0级的mspan为大对象定制。
查找空闲元素空间时,需要从mcache中找到对应级别的mspan,mspan中拥有allocCache字段,其作为一个位图用于标记span中的元素是否被分配。
在mcentral查找时,需要遍历两个链表mcentral scan和mcentral nonscan。这是因为可能有些span虽然被垃圾回收器标记为空闲了,但还没来得及清理,这些span在清理后还可以使用。
若在mcentral找不到可以用的span则需要到mheap中查找。mheap会首先查找每个逻辑处理器P中pageCache字段的cache。cache位图标记每个page是否被使用。
如果要分配的page过大或P的cache中找不到可用的page。就需要对mheap加锁,查找mheap管理的虚拟地址空间查看是否有可用的page, 这涉及GO语言对线性地址空间的位图管理。管理线性地址空间的位图结构叫做基数树。
这个树中每个节点都对应一个pallocSum,除了叶子节点之外,都包含连续8个字节点的内存信息。pallocSum由三部分组成,Start表示开头有多少连续内存页,Max表示最多有多少连续内存页,end表示有多少连续页。
并且我们每次查找不需要从根节点开始查找,我们有一个特别的字段searchAddr,只需要向searchAddr的地址后查找即可跳过已经查找的节点,减少查找时间。
当当前chunk没有连续的page时。需要从基数树从上到下进行查找。可以先计算pallocSum开头有多少连续空间,若不足则计算最大多少连续空间。若max>申请的空间,则表示有充足的地址,则需要到下一级查找具体的位置。如果max< 申请的空间,我们可以将两个节点组合起来一个更大的空间。
如果基数树没有充足的空间,则需要向操作系统申请内存。
GO语言mheap结构:
堆区的线性内存
spans
区域存储了指向内存管理单元 runtime.mspan 的指针,每个内存单元会管理几页的内存空间,每页大小为 8KB;bitmap
用于标识 arena
区域中的那些地址保存了对象,位图中的每个字节都会表示堆区中的 32 字节是否空闲;arena
区域是真正的堆区,运行时会将 8KB 看做一页,这些内存页中存储了所有在堆上初始化的对象;
对于任意一个地址,我们都可以根据 arena
的基地址计算该地址所在的页数并通过 spans
数组获得管理该片内存的管理单元 runtime.mspan,spans
数组中多个连续的位置可能对应同一个 runtime.mspan 结构。
Go 语言在垃圾回收时会根据指针的地址判断对象是否在堆中,并通过上一段中介绍的过程找到管理该对象的 runtime.mspan。这些都建立在堆区的内存是连续的这一假设上。这种设计虽然简单并且方便,但是在 C 和 Go 混合使用时会导致程序崩溃:
小对象分配路线:mcache->mcentral->mheap位图查找->mheap基数树查找->操作系统分配