内存逃逸是什么?
在go语言中,内存分配存在两个方式:堆分配;栈分配。
栈分配:是在函数调用时为局部变量分配内存,当函数返回时,这些内存会自动释放。
堆分配:通过 new 或者 make 函数动态分配内存,需要手动进行释放或者自动回收机制释放。
内存逃逸是指原先在栈上分配的内存被分配到堆上。这样导致函数结束时不能自动回收,只能通过垃圾回收器回收,对于性能影响较大。
内存逃逸的几种情况
1.返回指针导致内存逃逸
package mainimport "fmt"func createPointer() *int {x := 100 // 局部变量return &x // x 逃逸到堆上
}func main() {p := createPointer()fmt.Println(*p) // 100
}
为什么发生了逃逸?
x
是createPointer()
的局部变量,正常情况下函数返回后应该销毁- 但是
&x
返回了x
的地址,导致x
需要在main()
继续使用,不能放在栈上,必须逃逸到堆
2.切片或 map 赋值导致逃逸
package mainfunc main() {s := make([]int, 3) // 堆分配m := make(map[int]int) // 堆分配_ = s_ = m
}
为什么发生了逃逸?
make([]int, 3)
创建了切片,其底层数组可能存储在堆上(具体取决于大小)make(map[int]int)
创建的map
结构存储在堆上,因为map
需要在多个作用域间传递。
3.字符串和 interface{} 赋值导致逃逸
package mainimport "fmt"func printAny(i interface{}) {fmt.Println(i)
}func main() {x := "hello"printAny(x) // x 逃逸到堆
}
为什么发生了逃逸?
x
是string
,但printAny()
需要interface{}
- Go 需要将
x
装箱(boxing),创建interface{}
类型的值,而interface{}
可能存储在堆上
4.闭包捕获变量导致逃逸
package mainimport "fmt"func closure() func() int {x := 42return func() int {return x // x 逃逸到堆}
}func main() {f := closure()fmt.Println(f()) // 42
}
为什么发生了逃逸?
x
是closure()
的局部变量,但被匿名函数func() int
捕获- 由于
func() int
可能在closure()
结束后仍然被调用,x
必须分配到堆上。
如何避免内存逃逸?
避免返回指针
错误情况
// ❌ 发生逃逸
func bad() *int {
x := 42
return &x
}
改进(使用值返回,而不是指针)
func good() int {
x := 42
return x // 不逃逸
}
使用参数传递而不是使用闭包
func returnFunction(x int) func() int {
return func() int { return x }
}
使用 sync.Pool
复用对象
package main
import (
"fmt"
"sync"
)var pool = sync.Pool{
New: func() interface{} { return new(int) },
}func main() {
p := pool.Get().(*int) // 复用对象,减少堆分配
*p = 100
fmt.Println(*p)
pool.Put(p) // 归还对象
}
使用 strings.Builder
代替字符串拼接
package main
import (
"fmt"
"strings"
)func main() {
var sb strings.Builder // 避免字符串逃逸
sb.WriteString("Hello ")
sb.WriteString("World")
fmt.Println(sb.String())
}
总结
逃逸原因 | 例子 | 解决方案 |
---|---|---|
返回指针 | return &x | 返回值而不是指针 |
切片/Map | make([]int, 3) | 小数组可用 var arr [3]int |
interface{} | printAny(x) | 避免不必要的 interface{} |
闭包捕获变量 | return func() { return x } | 使用 参数传递 而不是 闭包 |
字符串拼接 | s += "hello" | 用 strings.Builder |