什么是内存逃逸
内存逃逸是编译器执行静态代码分析后,对内存管理进行的优化和简化。在编译原理中,分析指针动态范围的方法。即当一个对象的指针被多个方法或线程引用则称这个指针发生了逃逸。
栈和堆的对比
- 堆适合不可与之大小的内存分配,但是也因此付出的代价是分配速度较慢,而且会形成内存碎片。
- 栈内存分配则非常快,ta只需通过push指令并且会自动释放内存。
- 堆分配内存首先需要找到一块大小合适的内存块,之后要通过垃圾回收才能释放。
- 堆不会自动释放不使用的内存,这就会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销。
逃逸分析的作用
逃逸分析的作用就是尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上变量少了,会减轻内存分配的开销,同时也会减少垃圾回收的压力提高程序的运行速度。
逃逸分析原则
如果一个函数返回对一个变量的引用,那么这个变量就会发生逃逸。编译器会分析代码的特征和代码的生命周期,Go中的变量只有编译其可证明在函数返回后不再被引用才分配到栈上,其他情况下都是分配到堆上。
编译器根据变量是否被外部引用来决定是否逃逸:
a. 如果变量在函数外部没有引用,则优先存放在栈上
b. 如果变量在函数外部存在引用,则必定存放在堆上
逃逸例子
package main
import (
"fmt"
)
func bar() *int {
t := 3
return &t
}
func main() {
x := bar()
fmt.Println("x the value of : ", *x)
}
➜ go build -gcflags '-m -l' main.go
# command-line-arguments
./main.go:8:2: moved to heap: t
./main.go:14:13: ... argument does not escape
./main.go:14:14: "x the value of : " escapes to heap
./main.go:14:35: *x escapes to heap
➜
main函数中引用了bar函数的返回值,所以bar函数中的变量t发生了逃逸。因为fmt.Println()函数所接受的参数在编译器很难确定其具体类型,所以也发生了逃逸。
that's all