Question
Do you know about golang’s memory escape? Under what circumstances does a memory escape occur?
How to answer
golang program variables
will carry a set of verification data to prove whether its entire life cycle is fully known at runtime. If the variable passes these checks, it can be allocated on the
stack. Otherwise, it is said to escape
and must be allocated on the
heap.
Typical situations that can cause variables to escape to the heap:
- Return the local variable pointer in the method The local variable should originally be Allocate on the stack and deallocate on the stack. However, since it is referenced externally when returning, its life cycle is greater than the stack, and it overflows.
- Send a pointer or a value with a pointer to the channel. At compile time, there is no way to know which goroutine will receive data on the channel. So the compiler has no way of knowing when the variable will be released.
- Store pointers or values with pointers on a slice. A typical example is []*string. This causes the slice's contents to escape. Although the array behind it may be allocated on the stack, the value it references must be on the heap.
- The array behind slice is reallocated because its capacity (cap) may be exceeded during append. The place where the slice is initialized can be known at compile time, and it will initially be allocated on the stack. If the storage behind the slice is to be expanded based on runtime data, it will be allocated on the heap.
- Call methods on interface types. Calling methods on interface types is dynamically scheduled - the actual implementation of the method can only be known at runtime. Imagine a variable r of type io.Reader. Calling r.Read(b) will cause the value of r and the storage behind slice b to escape, so they will be allocated on the heap.
Example
- Let’s deepen our understanding through an example. Next, try to check the escape situation through
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) }
Executiongo 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
Descriptionnew(A)
escapes, which conforms to the first of the common situations mentioned above. -
./main.go:14:11: main a.s " world" does not escape
Descriptionb
The variable does not escape because it only exists within the method. Recycled at the end of the method. -
./main.go:15:9: b "!" escapes to heap
Descriptionc
Variable escapes throughfmt.Println(a .. .interface{})
All printed variables will escape. Interested friends can check why. - The above operation is actually called escape analysis. In the next article, I will talk to you about how to use a trickier method to prevent variables from escaping. It is convenient for everyone to show off in front of the interviewer.