go

Golang之内存逃逸

Golang之内存逃逸

Posted by Lerko on August 14, 2020

Golang内存分配方式

主要是堆(heap)和栈(stack)分配两种。

内存形式 特性
栈分配廉价 对于栈的操作只有入栈和出栈两种指令,属于静态资源分配。函数的内部中不对外开放的局部变量,只作用于函数中。
堆分配昂贵 堆中分配的空间,在结束使用之后需要垃圾回收器进行闲置空间回收,属于动态资源分配。1. 函数的内部中对外开放的局部变量。2. 变量所需内存超出栈所提供最大容量。

实例

package main
 
import(
  "fmt"
  "runtime"
  "time"
)
 
func foo() {
    runtime.GOMAXPROCS(1)
    for i := 0; i < 10; i++ {
        go func() {
            fmt.Print(i, " ")
        }()
    }
    runtime.Gosched()
    time.Sleep(time.Second)
}
 
func main() {
  foo();
}

最后运行结果是

➜  memory git:(master) ✗ go run sgap.go 
10 10 10 10 10 10 10 10 10 10 %

那么由于i存在于堆中,并没有随着foo函数结束而释放,并且i的值为10,这就解释了为什么运行结果为10个10了

查看gcflags可以看到i 逃逸到heap中了

➜  memory git:(master) ✗ go build -gcflags '-m' ./sgap.go  
# command-line-arguments
./sgap.go:16:17: inlining call to runtime.Gosched
./sgap.go:16:17: inlining call to runtime.checkTimeouts
./sgap.go:12:6: can inline foo.func1
./sgap.go:13:13: inlining call to fmt.Print
./sgap.go:20:6: can inline main
./sgap.go:11:6: moved to heap: i
./sgap.go:12:6: func literal escapes to heap
./sgap.go:13:13: i escapes to heap //i 逃逸到heap中了
./sgap.go:13:17: " " escapes to heap
./sgap.go:13:13: []interface {} literal does not escape
<autogenerated>:1: .this does not escape

那些情况会出现内存逃逸

指针指向的数据都是在堆上分配的。

  1. 发送指针或带有指针的值到 channel 中。

在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。

  1. 在一个切片上存储指针或带指针的值。

一个典型的例子就是 []*string。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。

  1. slice 的背后数组被重新分配了,因为 append 时可能会超出其容量(cap)。

slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。

  1. 在 interface 类型上调用方法。

在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r, 调用 r.Read(b) 会使得 r 的值和切片 b 的背后存储都逃逸掉,所以会在堆上分配。