go基础|青训营笔记

83 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天。今天我学习了Go语言的内存管理。Go语言中栈内存也叫协程栈或者调用栈,协程栈中第一个栈帧是goexit(),goexit()是为了退出后重新调度使用的,同时协程栈中还记录了协程的执行路径,例如下图中,do1()调用了do2()。

在函数中声明的局部变量,如果只是函数内部使用的话,那么这个变量会记录在协程栈里面。 C/C++中栈区和堆区是分开的,堆上的内存需要程序员自己去释放,栈上的内存由程序释放,但是Go语言中栈内存是从[堆内存]上申请的,初始空间为2KB,所以说Go协程栈位于Go堆内存上,而Go堆内存位于操作系统虚拟内存上。main()调用sum(a,b int)时需要传递参数,在栈帧里面参数的传递顺序是反的,传递参数时在自己的栈帧里开辟空间记录下要传递的参数,因为Go采用的是值传递。然后会记录sum(a,b int)返回后的指令,也就是上述代码中fmt.Println()。

运行sum(a,b int)函数时会首先在函数的栈帧中记录调用者的栈基址,意思就是当函数返回后需要返回到哪一个栈帧。当代码运行到sum = a + b,sum()函数会到main()函数的栈帧中寻找a、b的值,sum()函数返回时,会将返回值写回它的调用者的栈内存中预留的返回值空间,也就是上图中的sum函数返回值。由上述可知,协程栈记录了函数的执行路径(函数调用信息)和局部变量信息,所以协程栈被填满的原因就是函数调用太深或者局部变量太大。

如果局部变量太大导致协程栈空间不足,那么局部变量会逃逸到堆上。

如果函数调用太深倒是协程栈空间不足,那么会进行栈扩容。

协程在函数调用前会调用morestack判断栈空间是否足够,在调用函数之前要给下一个函数开辟新的栈空间,必要时对栈进行扩容,栈扩容的策略有分段栈和连续栈。