Go栈内存管理

在《GO堆内存管理》讲过,Go语言的runtime将堆内存空间分配为一个一个的arena,划分的arena里又会按需划分出不同的span。span对应的数据结构为mspan。根据状态的不同(mspan.state.s),span用途也不一样,用于堆内存的mspan状态为mSpanInUse,而用作栈内存的状态为mSpanManual。

为了提高栈内存分配效率,调度器初始化时,会初始化两个用于栈分配的全局对象。

  • stackpool面向32KB以下的栈分配,栈大小必须是2的幂,最小2KB。在linux环境下,stackpool提供了2KB、4KB、8KB、16KB四种规格的mspan链表。
  • stackLarge面向大于32KB的栈分配。也是一个mspan链表的数组,长度为25,mspan规则从8KB开始,之后每个链表的mspan规则都是前一个的两倍,8KB和16KB这两个链表实际上一直是空的,留着它们,是为了使用mspan包含页数的(以2为底)对数作为下标,初始化以后这些链表都是空的,

以上两种会作为全局栈缓存来使用。同堆内存分配一样,每个P也有用于栈分配的本地缓存,mcache.stackcache。同样是四种规则(2KB、4KB、8KB、16KB)的空闲内存块链表。

1、要分配栈内存时,对于小于32KB的栈空间,比如8KB,会优先使用当前P的本地缓存,如果对应规则的内存块链表为空,就从stackpool这里分配16KB的内存放到本地缓存stackcache中,然后继续从本地缓存分配,若是stackpool中对应的链表也为空,就从堆内存直接分配一个32KB的span,划分成对应的内存块大小放到stackpool中。

有些情况是无法使用本地缓存的stackcache。(比如关闭stackcache 或 当前M没有绑定的P 或 GC过程中)

再不能直接使用本地缓存的情况下,直接从stackpool分配。

2、对于要分配大于等于32KB的栈空间时,就计算需要的page数目,并以2为底求对数,将得到的结果作为stackLarge数组的下标,找到对应空闲的mspan链表,如果链表不为空,就拿过来一个用。如果链表为空,就直接从堆内存分配一个,拥有这么多页面的span,并把它整个用于分配栈内存。

栈增长

栈内存初始分配发生在goroutine创建时,由于初始栈大小都是2KB,在实际业务中可能不够用,所以需要实现在运行阶段动态增长栈的机制,goroutine的栈增长是通过编译器和runtime合作实现的。编译器会在函数的头部安插检测代码,检查当前剩余栈空间是否够用,不够用的时候,就调用runtime中的morestack_noctxt()函数来增长栈空间。栈空间是成倍增长的,需要增长时,就把当前栈空间大小乘以2,并把协程状态设置为_Gcopystack。接下来调用copystack函数,分配新的栈空间,拷贝旧栈上的数据,释放旧栈空间,最后恢复协程运行(_Grunning)。

栈收缩

唯一发起栈收缩的地方就是GC,GC通过scanstack函数,寻找标记root节点时,如果发现可以安全的收缩栈,就会执行栈收缩。不能马上执行时,就设置栈收缩标记(g.preemptShrink=true),等到协程检测到抢占标识(stackPreempt)时,再让出CPU之前会检查这个栈收缩标识,为true的话就会先进行栈收缩,再让出CPU。

结束的那些协程的栈空间该如何回收利用呢?

从协程运行结束时说起,常规goroutine结束运行时,会被放入调度器对象这里的空间G队列中(sched.gFree)。这里的空闲协程分为两种,一种有协程栈(sched.gFree.stack),一种没有协程栈(sched.gFree.noStack)。创建协程时(runtime.newproc)会优先使用有栈的协程,免去额外再分配。

不过常规goroutine运行结束时,都有协程栈,应该进那个队列呢?

如果协程栈没有增长过(2KB),就把这个协程放到有栈的空闲G队列中。如果协程栈增长过(>2KB),就把协程释放掉,再把协程放入到没有栈的空闲G队列中,而这些空闲协程的栈也会在执行GC执行markroot时被释放掉,到时候,这些协程也会加入到没有栈的空闲G队列中。

这些栈释放到了哪里?

  • 小于32KB的栈会先放回本地缓存,如果本地缓存对应链表中栈空间总和大于32KB,就把一部分放回stackpool中,本地这个链表值保存16KB。如果本地缓存不可用,也会直接放回stackpool中。而且发现这个mspan中所有的内存块都被释放了,就会把它归还给堆内存。
  • 大于等于32KB的栈,如果当前处于GC清理阶段(gcphase == _GCoff),就直接释放到堆内存。反之就先把它放回stackLarge中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值