go的内存逃逸,为什么逃逸,怎么避免
Laiyong Wang Lv6

在 Go 语言中,“内存逃逸”指的是本来可以分配在栈上的变量,由于它的引用超出了局部作用域(或者编译器认为有这种风险),而被分配到了堆上,从而增加了 GC 的负担和内存分配开销。


为什么会发生内存逃逸?

  1. 返回局部变量的指针
    如果一个函数返回局部变量的地址,编译器就会认为这个变量在函数外部还可能被使用,因而将其分配到堆上。

  2. 闭包捕获变量
    当匿名函数(闭包)捕获了外部局部变量,并在函数外部被使用时,局部变量会逃逸到堆上。

  3. 赋值给接口类型
    将局部变量赋值给接口变量时,由于接口底层存储的是指向值的指针,编译器可能会将局部变量分配到堆上。

  4. 多 goroutine 共享
    如果局部变量在多个 goroutine 中共享使用,编译器也会将其放到堆上,以保证数据的生命周期正确。


如何避免内存逃逸?

  1. 使用值传递
    如果不需要共享或修改,尽量用值传递而非指针传递,减少不必要的堆分配。

  2. 避免不必要的闭包捕获
    设计时注意局部变量不要被无意中捕获到闭包中,可以将变量提前复制到局部副本。

  3. 谨慎对待接口赋值
    尽量避免将局部变量直接赋值给接口变量。如果可以,采用具体类型传递。

  4. 利用编译器逃逸分析工具
    使用命令 go build -gcflags=-mgo run -gcflags=-m 查看逃逸分析报告,定位哪些变量发生了逃逸,再做针对性优化。


“Go 编译器在编译时会进行逃逸分析,决定一个局部变量是否能安全地分配在栈上。当变量的地址被返回、赋值给接口或闭包捕获时,编译器会认为该变量可能在函数外部继续使用,从而将其分配到堆上。这种‘逃逸’虽然能保证程序正确运行,但堆上分配的开销较大,会增加 GC 的负担。为了避免不必要的内存逃逸,我们可以:

  • 尽量使用值传递,避免返回局部变量指针;
  • 控制闭包对局部变量的捕获;
  • 避免将局部变量赋值给接口;
  • 并利用 go build -gcflags=-m 命令查看逃逸分析报告进行优化。”
$li-margin-bottom = 0.8rem $post-tool-button-width = 2.5rem .post-tools-container { padding-top var(--component-gap) .post-tools-list { li { margin-bottom $li-margin-bottom &:last-child { margin-bottom 0 } } li.tools-item { position relative box-sizing border-box width $post-tool-button-width height $post-tool-button-width color var(--text-color-3) font-size 1.2rem background var(--background-color-1) border-radius 50% box-shadow 2px 2px 5px var(--shadow-color) cursor pointer &:hover { box-shadow 2px 2px 8px var(--shadow-hover-color) } i { color var(--text-color-3) } &:hover { color var(--background-color-1) background var(--primary-color) i { color var(--background-color-1) !important } } &.toggle-show-toc { display none } &.go-to-comments { .post-comments-count { position absolute top 0 right -1rem display none align-items center justify-content center box-sizing border-box min-width 1.1rem height 1.1rem padding 0 0.2rem color var(--badge-color) font-size 12px background var(--badge-background-color) border-radius 0.4rem +keep-tablet() { display none !important } } } } li.status-item { width $post-tool-button-width height $post-tool-button-width color var(--text-color-3) font-size 1.6rem cursor pointer &.post-lock { cursor default .fa-lock-open { display none color var(--keep-success-color) } &.decrypt { cursor pointer .fa-lock-open { display block } .fa-lock { display none } } } } } }