分段栈
在Go1.3之前,所有goroutine在初始化时都会分配一块固定大小的内存空间。
- 在固定8KB或者满足其他条件下,会在全局的栈缓存链表中找到空闲的内存块作为新goroutine的栈空间返回
- 其余情况,会在堆上申请一块合适的内存
所有栈空间会以链表的形式串联起来
分段栈能够按需为当前goroutine分配内存,并及时减少内存占用,但
- 如果当前goroutine栈几乎充满,那么任意的函数调用都会触发栈扩容,当函数返回后又会触发栈的收缩,如果在一个循环中调用函数,栈的分配和释放就会造成巨大的额外开销,这被称为热分裂问题(Hot split)
- 一旦 Goroutine 使用的内存越过了分段栈的扩缩容阈值,运行时会触发栈的扩容和缩容,带来额外的工作量
连续栈
连续栈核心原理是当当前栈内存不足时,会触发中断,从而分配更大充足的栈空间,并迁移原栈内容到新栈
迁移过程会被指针也同样迁移过来,不过没关系根据逃逸分析不变性——指向栈对象的指针不能存在于堆中,所以指向栈中变量的指针只能在栈上
与堆类似,栈也有全局和局部栈空间
- 全局栈空间
- runtime.stackpool(全局栈缓存):分配小于32KB
- runtime.stackLarge(全局大栈缓存):分配大于32KB。如果空间不足会在堆上申请
- 线程缓存:分配小于32KB
此外,除了扩容,连续栈还会在已使用的栈空间未超过了总可用空间的四分之一,将新栈收缩到原来的一半
Ref
- https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-stack-management/