image.png

简介

在Go中堆内存的分配和释放不需要去手动申请和释放,Go语言引入了GC回收的机制,GC机制会对位于堆上的对象进行自动管理,当某个对象不可达时(既没有其对象引用它时),它将会被回收并被重用。引入GC机制可以让开发者降低对内存管理的心智压力
image.png
堆和栈区别:

    • Go中全局的堆(heap)空间用来动态分配内存,有GC机制,开销较大
    • goroutine中的栈(stack)、局部变量的栈空间,开销很小


内存逃逸

在程序中,每个函数块都有自己的内存区域存放自己的局部变量(内存占用少)、返回地址、返回值等数据,这一块内存区域有特定的结构和寻址方式,寻址起来十分迅速,开销很少。这一块内存地址也称为栈。栈是线程级别,大小在创建时候已经确定。当变量太大时,会“逃逸”到堆上,这种现象称为内存逃逸。简单地说,局部变量通过堆分配和回收,就叫内存逃逸。

内存逃逸危害

堆是一块没有特定结构,也没有固定大小的内存区域,可以根据需要进行调整。全局变量,内存占用较大的局部变量,函数调用结束后不能立刻回收的局部变量都会存在堆里面。变量在堆上的分配和回收都比在栈上开销大的多。对于 Go 这种带 GC 的语言来说,会增加 GC 压力,同时也容易造成内存碎片

避免内存逃逸

  • 对于比较小型的数据,尽量传值而不是传指针,避免内存逃逸
  • 避免使用长度不固定的slice切片,在编译期无法确定切片长度,只能将切片使用堆分配
  • interface调用方法会发生内存逃逸,在热点代码片段,谨慎使用

分析内存逃逸发生

指令

  • go build -gcflags=-m main.go
  • go tool compile -m main.go

出现escapes to heap,则表示发生了逃逸
image.png

好处

  • 通过逃逸分析,可以确认特定变量是分配在堆内存还是栈内存,提高了程序性能。
  • 降低GC压力,不逸出的变量可以及时回收。

逃逸例子

example1:指针逃逸

  1. package main
  2. type Person struct {
  3. Name string
  4. }
  5. func SetPerson(name string) *Person {
  6. //new(Person) escapes to heap 这里发生了逃逸
  7. p := new(Person)
  8. p.Name = name
  9. return p
  10. }
  11. func main() {
  12. SetPerson("reggie")
  13. }

example2:堆栈空间不足逃逸

  1. func main() {
  2. //10000的空间开销超出了最大的8192栈空间,逃逸到堆上
  3. //main.go:30:11: make([]int, 10000, 10000) escapes to heap
  4. s := make([]int, 10000, 10000)
  5. for index, _ := range s {
  6. s[index] = index
  7. }
  8. }

通常64位的计算机栈大小是8MB,可以通过以下指令通过stack size查看

  • 指令
    • ulimit -a

image.png

example3: 动态类型逃逸

  1. package main
  2. import "fmt"
  3. func main() {
  4. //发生逃逸
  5. fmt.Println("hello golang")
  6. }

参数类型为… interface{} ,在编译时很难确定参数的具体类型,这种情况也会产生逃逸。内置的Println方法中引用了interface{}