共计 2091 个字符,预计需要花费 6 分钟才能阅读完成。
问题
晓得 golang 的 内存逃逸 吗?什么状况下会产生内存逃逸?
怎么答
golang 程序变量
会携带有一组校验数据,用来证实它的整个生命周期是否在运行时齐全可知。如果变量通过了这些校验,它就能够在 栈上
调配。否则就说它 逃逸
了,必须在 堆上调配
。
能引起变量逃逸到堆上的 典型状况:
- 在办法内把局部变量指针返回 局部变量本来应该在栈中调配,在栈中回收。然而因为返回时被内部援用,因而其生命周期大于栈,则溢出。
- 发送指针或带有指针的值到 channel 中。 在编译时,是没有方法晓得哪个 goroutine 会在 channel 上接收数据。所以编译器没法晓得变量什么时候才会被开释。
- 在一个切片上存储指针或带指针的值。 一个典型的例子就是 []*string。这会导致切片的内容逃逸。只管其前面的数组可能是在栈上调配的,但其援用的值肯定是在堆上。
- slice 的背地数组被重新分配了,因为 append 时可能会超出其容量(cap)。 slice 初始化的中央在编译时是能够晓得的,它最开始会在栈上调配。如果切片背地的存储要基于运行时的数据进行裁减,就会在堆上调配。
- 在 interface 类型上调用办法。 在 interface 类型上调用办法都是动静调度的 —— 办法的真正实现只能在运行时晓得。想像一个 io.Reader 类型的变量 r , 调用 r.Read(b) 会使得 r 的值和切片 b 的背地存储都逃逸掉,所以会在堆上调配。
举例
- 通过一个例子加深了解,接下来尝试下怎么通过
go build -gcflags=-m
查看逃逸的状况。
package main
import "fmt"
type A struct {s string}
// 这是下面提到的 "在办法内把局部变量指针返回" 的状况
func foo(s string) *A {a := new(A)
a.s = s
return a // 返回局部变量 a, 在 C 语言中妥妥野指针,但在 go 则 ok,但 a 会逃逸到堆
}
func main() {a := foo("hello")
b := a.s + "world"
c := b + "!"
fmt.Println(c)
}
执行go build -gcflags=-m main.go
go build -gcflags=-m main.go
# command-line-arguments
./main.go:7:6: can inline foo
./main.go:13:10: inlining call to foo
./main.go:16:13: inlining call to fmt.Println
/var/folders/45/qx9lfw2s2zzgvhzg3mtzkwzc0000gn/T/go-build409982591/b001/_gomod_.go:6:6: can inline init.0
./main.go:7:10: leaking param: s
./main.go:8:10: new(A) escapes to heap
./main.go:16:13: io.Writer(os.Stdout) escapes to heap
./main.go:16:13: c escapes to heap
./main.go:15:9: b + "!" escapes to heap
./main.go:13:10: main new(A) does not escape
./main.go:14:11: main a.s + "world" does not escape
./main.go:16:13: main []interface {} literal does not escape
<autogenerated>:1: os.(*File).close .this does not escape
./main.go:8:10: new(A) escapes to heap
阐明new(A)
逃逸了, 合乎上述提到的常见状况中的第一种。./main.go:14:11: main a.s + "world" does not escape
阐明b
变量没有逃逸,因为它只在办法内存在,会在办法完结时被回收。./main.go:15:9: b + "!" escapes to heap
阐明c
变量逃逸,通过fmt.Println(a ...interface{})
打印的变量,都会产生逃逸,感兴趣的敌人能够去查查为什么。- 以上操作其实就叫 逃逸剖析 。 下篇文章,跟大家聊聊怎么用一个比拟 trick 的办法使变量不逃逸。不便大家在面试官背后秀一波。
文章举荐:
- golang 面试题:对曾经敞开的的 chan 进行读写,会怎么样?为什么?
- golang 面试题:对未初始化的的 chan 进行读写,会怎么样?为什么?
- golang 面试题:reflect(反射包)如何获取字段 tag?为什么 json 包不能导出公有变量的 tag?
- golang 面试题:json 包变量不加 tag 会怎么样?
- golang 面试题:怎么防止内存逃逸?
- golang 面试题:简略聊聊内存逃逸?
- golang 面试题:字符串转成 byte 数组,会产生内存拷贝吗?
- golang 面试题:翻转含有
中文、数字、英文字母
的字符串 - golang 面试题:拷贝大切片肯定比小切片代价大吗?
- golang 面试题:能说说 uintptr 和 unsafe.Pointer 的区别吗?
正文完