欢迎来到张胤尘的技术站
技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌
什么是内存逃逸?内存逃逸对程序有什么样的影响?如何避免?
内存逃逸是指在函数内部创建的变量或对象,在函数结束后仍然被其他部分引用或持有,从而脱离了函数的作用域。在 golang
中,编译器会根据逃逸分析的结果,将变量分配到栈或堆上。如果变量的生命周期超出了函数的作用域,就会被分配到堆上,这种现象被称为内存逃逸。
例如:
package main
import (
"fmt"
)
func createSlice() []int {
var s []int
for i := 0; i < 10; i++ {
s = append(s, i)
}
return s
}
func main() {
slice := createSlice()
fmt.Println(slice)
}
在上述代码中,在 createSlice
函数的内部创建了一个局部变量 s
,并在循环中向其中添加了元素。最后,函数返回了这个切片。在正常情况下,这个局部变量 s
会被分配到函数栈上,但是逃逸分析会检测到切片 s
的底层数组需要在函数外被访问,因此会将底层数组分配到堆上,而不是栈上。
下面通过 go build
命令可以更加直观的看出内存逃逸的表现。如下所示:
$ go build -gcflags="-m" test.go
# command-line-arguments
./test.go:7:6: can inline createSlice
./test.go:16:22: inlining call to createSlice
./test.go:17:13: inlining call to fmt.Println
./test.go:17:13: ... argument does not escape
./test.go:17:14: slice escapes to heap
在以上的输出结果中,slice escapes to heap
这是关键信息,表明 createSlice
函数返回的切片 slice
被分配到了堆上。
内存逃逸对程序的影响主要体现在以下几个方面:
gc
的回收。因此,程序的堆内存占用会增加。gc
管理。当堆内存增加时,gc
的运行频率也会增加,这会导致程序的 STW
或额外的性能开销。频繁的 gc
可能会显著降低程序的响应速度和吞吐量。悬空指针是指指针曾经指向一个有效的内存位置,但该内存已被释放或回收,导致指针变得无效。尽管指针仍然保存着原来的地址,但访问该地址会产生未定义行为,因为该地址可能已经被分配给其他对象或成为不可访问的区域。
总的来说,内存逃逸对程序的影响是多方面的,既有积极的一面,也有消极的一面。在实际开发中,合理管理内存逃逸是优化程序性能和稳定性的关键。以下是一些优化建议:
golang
的逃逸分析工具和性能分析工具,找出并优化不必要的内存逃逸。在之前的代码中,由于这个场景中切片的逃逸是不可避免的(因为需要返回切片),但可以通过以下方式减少不必要的堆分配:
package main
import (
"fmt"
)
func createSlice() []int {
// 预分配足够的容量,避免多次扩容
s := make([]int, 10)
for i := 0; i < 10; i++ {
s[i] = i
}
return s
}
func main() {
slice := createSlice()
fmt.Println(slice)
}
make([]int, 10)
预分配切片的容量,避免在 append
过程中多次扩容。扩容会导致额外的堆分配和数据拷贝。这种优化可以减少堆分配的频率和大小,但无法完全避免切片的逃逸。撒花!
如果本文对你有帮助,就点关注或者留个
如果您有任何技术问题或者需要更多其他的内容,请随时向我提问。