自学内容网 自学内容网

Go语⾔的栈空间管理

Go语言的栈空间管理是其运行时(runtime)的一个重要组成部分,它负责维护函数调用所需的栈内存。以下是对Go语言栈空间管理的详细阐述:

栈空间的基本概念

栈空间一般由编译器自动进行管理,负责维护函数调用所需要的东西(参数、返回地址、返回值、局部变量)。栈和堆都是对不同功能、不同区域的内存的划分。栈空间底层的内存管理实际上涉及到底层的内存分配和寄存器操作。

栈寄存器

Go语言的汇编代码包含BP和SP两个栈寄存器,它们分别存储了栈的基址指针和栈顶的地址。栈内存与函数调用的关系非常紧密,BP和SP之间的内存就是当前函数的调用栈。

逃逸分析

在C语言和C++这类需要手动管理内存的编程语言中,将对象或者结构体分配到栈上或者堆上是由工程师自主决定的,这也为工程师的工作带来了挑战。如果工程师能够精准地为每一个变量分配合理的空间,那么整个程序的运行效率和内存使用效率一定是最高的。然而,手动分配内存会导致以下问题:

  • 不需要分配到堆上的对象分配到了堆上,浪费内存空间。
  • 需要分配到堆上的对象分配到了栈上,导致悬挂指针,影响内存安全。

为了避免这些问题,Go语言的编译器使用逃逸分析来决定哪些变量应该在栈上分配,哪些变量应该在堆上分配。逃逸分析遵循以下两个不变性:

  • 指向栈对象的指针不能存在于堆中。
  • 指向栈对象的指针不能在栈对象回收后存活。

栈空间的管理过程

Go语言的栈空间管理过程分为四个部分:栈初始化、栈分配、栈扩容和栈缩容。

  1. 栈初始化:在Go程序的启动时,会初始化两个全局变量用于栈空间的分配:stackpoolstackLargestackpool用于分配小于32KB的栈空间,而stackLarge用于分配大于32KB的栈空间。这两个全局变量与内存管理单元分配下的不同组合密切相关。
  2. 栈分配:在创建新的goroutine时,会为其分配一个初始的栈空间。这个栈空间的大小通常是2KB(在Go 1.4及以后版本中)。如果栈空间较小,会从stackpool或线程的本地缓存(mcache)中进行分配。如果栈空间较大,会从stackLarge中获取。如果stackLarge也没有足够的空间,会从堆上申请一片足够大的内存空间。
  3. 栈扩容:当goroutine调用的函数层级或局部变量需要的栈空间越来越多时,如果当前栈空间不足,就会触发栈扩容。栈扩容的过程包括保存一定信息、调用newstack创建新的栈、拷贝旧栈的内容到新栈、调整栈顶和栈底地址以及更新所有指向栈变量的指针地址。如果需要的栈空间大于最大栈空间,就会触发栈溢出错误。
  4. 栈缩容:为了优化内存使用,Go语言还会在适当的时候进行栈缩容。栈缩容的条件是新栈大小为原有的一半且新栈空间大于2KB。栈缩容的过程与栈扩容类似,但方向相反。

栈空间管理的优化

Go语言的栈空间管理采用了多种优化技术来提高效率和减少开销:

  • 分段栈与连续栈:Go语言最初使用分段栈来管理栈空间,但这种方法存在热分裂问题。为了解决这个问题,Go语言引入了连续栈机制。连续栈在栈空间不足时,会分配一个更大的栈空间并将原栈中的所有值都迁移到新栈中。这样可以避免频繁的栈分配和释放带来的额外开销。
  • 本地缓存:为了提高栈内存分配效率,每个线程(P)都有一个用于栈分配的本地缓存(mcache)。这个本地缓存包含了四种规格的空闲内存块链表(2KB、4KB、8KB、16KB),用于快速分配栈空间。
  • 垃圾回收信息:在进行栈复制时,Go语言会利用垃圾回收信息来更新栈中的指针地址。这可以确保在移动栈时,所有指向栈内内容的指针都能正确地指向新的地址。

总之,Go语言的栈空间管理是一个复杂而高效的过程,它结合了多种技术和优化手段来确保程序的正确性和性能。


原文地址:https://blog.csdn.net/sheji888/article/details/144681790

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!