所谓逃逸分析(Escape analysis)是指由编译器决定内存分配的位置,不需要程序员指定。函数中申请一个新的对象
- 如果分配在栈中,则函数执行结束可自动将内存回收
- 如果分配在堆中,则函数执行结束可交给GC(垃圾回收)处理
1 逃逸策略
每当函数中申请新的对象,编译器会跟据该对象是否被函数外部引用来决定是否逃逸:
- 如果函数外部没有引用,则优先放到栈中
- 如果函数外部存在引用,则必定放到堆中
注意,对于函数外部没有引用的对象,也有可能放到堆中,比如内存过大超过栈的存储能力
2 逃逸场景
- 指针逃逸:在函数外部被使用
函数StudentRegister()内部s为局部变量,其值通过函数返回值返回,s本身为一指针,其指向的内存地址不会是栈而是堆,这就是典型的逃逸案例。
package main
type Student struct {
Name string
Age int
}
func StudentRegister(name string, age int) *Student {
s := new(Student) // The local variable s escapes to the heap
s.Name = name
s.Age = age
return s
}
func main() {
student := StudentRegister("Jim", 18)
}
- 栈空间不足:栈空间不足以存放当前对象时或无法判断当前切片长度时会将对象分配到堆中
下面代码Slice()函数中分配了一个1000个长度的切片,是否逃逸取决于栈空间是否足够大。
package main
func Slice() {
s := make([]int, 1000, 1000)
for index := range s {
s[index] = index
}
}
func main() {
Slice()
}
- 动态类型逃逸:很多函数参数为interface类型,编译期间很难确定其参数的具体类型
变量s只是一个string类型变量,调用fmt.Println()时会产生逃逸:
package main
import "fmt"
func main() {
s := "Escape"
fmt.Println(s)
}
- 闭包中引用的对象
该函数返回一个闭包,闭包引用了函数的局部变量a和b,使用时通过该函数获取该闭包,然后每次执行闭包都会依次输出Fibonacci数列。
Fibonacci()函数中原本属于局部变量的a和b由于闭包的引用,不得不将二者放到堆上,以致产生逃逸。
package main
import "fmt"
func Fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
func main() {
f := Fibonacci()
for i := 0; i < 10; i++ {
fmt.Printf("Fibonacci: %d\n", f())
}
}
3 逃逸总结
- 栈上分配内存比在堆中分配内存有更高的效率
- 栈上分配的内存不需要GC处理
- 堆上分配的内存使用完毕会交给GC处理
- 逃逸分析目的是决定内分配地址是栈还是堆
- 逃逸分析在编译阶段完成