浅谈Go语言中的堆和栈

273 阅读3分钟

1 数据结构及特点

栈(Stack)是一种线性结构,它的内存空间是连续的,类似于一堆盘子叠在一起,后进先出。每个线程都会有自己的栈,用来存储函数调用时的临时数据,如局部变量、参数、返回值等。栈的内存空间由系统自动分配和回收,具有自动管理的特性。因为栈的内存空间是连续的,所以访问速度非常快,同时由于系统自动管理,所以也不容易出现内存泄漏和悬垂指针等问题。

堆(Heap)是一种动态分配内存的机制,它的内存空间是不连续的,类似于一大堆散装货物。在堆中分配的内存需要手动申请和释放,因此需要开发者自己管理。堆的内存空间通常用于存储比较大的数据结构,如动态数组、哈希表、二叉树等。堆的内存空间是不连续的,因此访问速度相对较慢,同时由于需要手动管理,容易出现内存泄漏和悬垂指针等问题。

2 Go中的堆和栈

栈主要用来存储值类型的数据,如整数、浮点数、布尔值等。因为值类型的数据大小是固定的,所以可以直接分配在栈上,访问速度非常快。

堆主要用来存储引用类型的数据,如字符串、切片、字典等。因为引用类型的数据大小是不固定的,所以需要动态分配内存,通常在堆上进行。同时,由于引用类型的数据通常需要共享和修改,因此使用指针来进行引用和操作,从而避免了复制大量的数据。

可以看出,栈的性能会更好——不需要额外的垃圾回收机制(离开该作用域,它们的内存就会被自动回收),CPU可以连续缓存(内存空间是连续的)。

3. Go中的逃逸现象

该现象就是变量从栈上逃逸到堆上。理解这种现象,在编码的时候,该逃的可以逃,不该逃的要去避免。主要包括下面三种情况:

  1. 当一个引用类型的值作为参数传递给一个函数时,它会被复制一份,而这份复制的值通常会在堆上分配内存。
  2. 当一个引用类型的值被赋值给另一个变量时,也会导致它在堆上分配内存。
  3. 当一个函数的返回值是指针时,也会导致它在堆上分配内存。

即使引用类型的值被分配在堆上,也可以通过一些技巧来减少内存分配的开销,如使用指针类型或者尽量避免频繁地创建和销毁对象。另外,内存池也可以用来管理一些频繁创建和销毁的引用类型对象,从而提高程序的性能。