MST

星途 面试题库

面试题:Go语言运行时内存管理中堆和栈的区别

在Go语言运行时的内存管理体系里,阐述堆内存和栈内存分别用于存储哪些类型的数据,并且说明它们在内存分配和回收机制上有何不同。
11.7万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

堆内存存储的数据类型

在Go语言中,堆内存通常用于存储以下类型的数据:

  1. 动态分配的数据:例如通过newmake关键字创建的对象,像切片(slice)、映射(map)、结构体指针等。这些数据的大小在编译时无法确定,需要在运行时动态分配内存。例如:
s := make([]int, 10)
m := make(map[string]int)
var ptr *struct {
    a int
    b string
} = new(struct {
    a int
    b string
})
  1. 逃逸分析后确定的需要在堆上分配的数据:Go编译器会进行逃逸分析,如果一个变量在函数返回后依然需要被访问,那么它就会被分配到堆上。例如:
func foo() *int {
    var a int = 10
    return &a
}

这里的变量a原本定义在函数内部,但由于返回了其指针,使得该变量在函数返回后依然可能被使用,因此会被分配到堆上。

栈内存存储的数据类型

栈内存主要用于存储以下类型的数据:

  1. 函数局部变量:在函数内部定义的非指针类型的基本数据类型变量,如整数(int)、浮点数(float)、布尔值(bool)、字符串(string,若字符串内容较小也可能在栈上)等,以及函数参数和返回值。例如:
func bar() {
    var num int = 20
    var flag bool = true
    var str string = "hello"
}
  1. 较小的结构体:如果结构体的大小和复杂度在一定范围内,且没有发生逃逸(例如没有返回结构体指针等情况),则该结构体也会存储在栈上。例如:
type Point struct {
    x int
    y int
}

func baz() {
    p := Point{1, 2}
}

内存分配机制的不同

  1. 堆内存分配:堆内存的分配相对复杂。Go语言运行时使用一种称为malloc的内存分配器(在底层实现中)。当需要在堆上分配内存时,运行时系统会从堆内存池中寻找合适大小的空闲块。如果没有合适的空闲块,可能会触发堆内存的扩展。分配过程涉及到查找空闲链表、分割内存块等操作,因此相对较慢。
  2. 栈内存分配:栈内存的分配非常高效。栈的增长方向是固定的(通常是向下增长),当一个函数被调用时,为该函数的局部变量分配栈空间只需要简单地移动栈指针即可,这个操作在现代处理器上是非常快速的。栈空间的大小在编译时可以大致确定(虽然Go语言支持动态栈扩展,但一般情况下每个函数调用所需的栈空间在编译时已有估算)。

内存回收机制的不同

  1. 堆内存回收:Go语言使用垃圾回收(GC)机制来回收堆内存。垃圾回收器会定期扫描堆内存,标记所有可达对象(即程序中还在使用的对象),然后回收那些不可达对象所占用的内存空间。Go语言的垃圾回收器采用三色标记法等算法来实现高效的垃圾回收。垃圾回收过程会暂停程序的运行(STW,Stop The World),虽然Go语言的垃圾回收器不断优化以减少STW的时间,但相比栈内存回收,堆内存回收仍然是一个相对复杂且会影响程序性能的过程。
  2. 栈内存回收:栈内存的回收非常简单。当一个函数返回时,其对应的栈帧会被自动销毁,栈指针会恢复到函数调用前的位置,栈空间所占用的内存就被释放了。这个过程是由程序的执行流程自然控制的,不需要额外的垃圾回收机制,因此栈内存回收是非常高效的。