2.6 简述一下 Go 栈空间的扩容/缩容过程?

扩容流程

为啥会有栈空间扩容

由于当前的 Go 的栈结构使用的是连续栈,并且初始值才 2k 比较小,因此随着函数的调用层级加深,Go 的初始栈空间就可能不够用,不够用的话,就会触发栈空间的扩容。

栈空间扩容啥时会触发

编译器会为函数调用插入运行时检查runtime.morestack,它会在几乎所有的函数调用之前检查当前goroutine 的栈内存是否充足,如果当前栈需要扩容,会调用runtime.newstack 创建新的栈。

而新的栈空间,是旧栈空间大小(通过保存在goroutine中的stack信息里记录的栈区内存边界计算出来的)的两倍,但最大栈空间大小不能超过 maxstacksize ,也就是 1G。

缩容流程

为啥会有栈空间缩容

在函数返回后,对应的栈空间会回收,如果调用栈比较深,那么随着函数一个一个返回,回收的栈空间会越来越多。假设在调用栈最深的时候,整体的栈空间扩容到了 100M,那么随着函数的返回,到某一个函数的时候,100M 的栈空间只有 1M 是实际占用的,内存利用率只有区区的 1% ,实在太浪费了。

栈空间缩容啥时会触发

因此在垃圾回收的时候,有必要检查一下栈空间里内存利用率,当利用率低于 25% 时,就要开始进行缩容,缩容成原来的栈空间的 50%,但同时也不能小于栈空间的原始值即最小值,2KB。

相同点

不管是扩容还是缩容,都是使用 runtime.copystack 函数来开辟新的栈空间,然后将旧栈的数据全部拷贝至新的栈空间,并调整原来指针的指向。