堆栈(内存分配)

堆(Heap)和栈(Stack) 都是在计算机中不同的数据结构的抽象类型。在应用程序内存分配中,一般来说,堆栈一类特殊的存储区域,用来存放数据和地址。

  • 堆(Heap)

在编程中,堆(Heap)是应用程序在运行的时候请求操作系统分配给自己内存,以 C/C++ 程序为例,使用时需要我们主动申请(通过 malloc/New),用完主动释放,或者是程序结束时由操作系统回收,会产生内存碎片。

  • 栈(Stack)

栈(Stack)是计算机内存的特定区域,一般 CPU 自动分配释放,数据结构特点是先进先出(FIFO—first in first out)。由于 CPU 可以高效组织内存,读写栈变量会非常快,并且不会产生内存碎片。

  • 堆(Heap)和栈(Stack)的区别
  1. 栈一般由操作系统来分配和释放,堆由程序员通过编程语言来申请创建与释放;
  2. 栈用来存放函数的参数、返回值、局部变量、函数调用时的临时上下文等,只要是局部的、占用空间确定的数据,一般都存放在 Stack,否则就放在 Heap;
  3. 栈的访问速度相对比堆快;
  4. 一般来说,Stack 是线程独占的,Heap 是线程共用的;
  5. Stack 创建的时候,大小是确定的,Heap 的大小是不确定的,可以自由增加。

现在写程序变负担变得越来越小,很大原因是在传统的 C/C++ 语言中,我们申请的内存空间要自己管理或释放,稍微不严谨,就会造成内存泄露。尤其在混合了多线程编程之后,更容易犯错。

在 Java、Go 等语言中,提供了一种主动释放申请的内存空间的功能,这就是垃圾回收机制,具有这种机制的语言大大解放了程序员负担。

Go 的堆栈

C/C++ 线程中,多数架构上默认线程栈大小都在 2MB ~ 4MB 左右,如果程序同时运行几百个甚至几千个线程,会占用大量的内存空间并带来其他的额外开销,Go 语言在设计时认为执行上下文是轻量级的,所以它在用户态实现 Goroutine 作为执行上下文,最小的栈空间只有 2KB。所以非常高效。

在 C 语言中,这样一段代码是错误的:

#include "stdio.h"

int *test(void)
{
    int a = 1;
    return &a;
}
int main()
{

}

在 Go 中, 却完全没有问题。

在 Go 中, 却完全没有问题。

package main

import "fmt"

func test() *int {
  a := 1
  return &a
}
func main() {
  intVal := test()
  fmt.Println(intVal)
}

通过分析我们可以发现:


第 6 行代码的的变量 a 分配到了堆上。

这是为什么呢?

Go 中变量分配在栈还是堆上完全由编译器决定,而看起来应该分配在栈上的变量,如果生命周期发生改变,被分配在了堆上,就说它发生了逃逸。编译器会自动地去判断变量的生命周期是否延长,整个判断的过程就叫逃逸分析。

Go 内存逃逸分析的情况

  • 切片扩容或容量太大,栈空间不足,逃逸到堆上。
package main

func main() {
  s1 := make([]int, 10000, 10000)
  _ = s1
}
  • 局部变量在函数调用完后还被其他地方使用 比如函数返回了局部变量的指针,函数形成闭包。
package main

func foo() func() int {
  val := 1

  return func() int {
    val += 1
    return val
  }
}

func main() {
  foo()
}
  • 在 slice 或 map 中存储指针
package main

func main() {
intVal := 10
var ls []*int
ls = append(ls, &intVal)
}
  • 向 channel 发送指针数据 在编译时,编译器无法知道channel 中的数据会被哪个 goroutine 接收,无法知道什么时候释放。
package main

func main() {
ch1 := make(chan *int, 1)
y := 5
py := &y
ch1 <- py
}


  • 在 interface 类型上调用方法。

在 interface 类型上调用方法时会把 interface 变量使用堆分配,因为方法的真正实现只能在运行时知道。

package main

type foo interface {
  fooFunc()
}
type foo1 struct{}

func (f1 foo1) fooFunc() {}

func main() {
  var f foo
  f = foo1{}
  f.fooFunc()
}