From b6063cb9f9ef4560c68156ed409e4cee56d6a78d Mon Sep 17 00:00:00 2001 From: Spirale <2635131263@qq.com> Date: Thu, 23 Jul 2026 00:07:16 +0800 Subject: [PATCH] =?UTF-8?q?docs(go):=20=E6=B7=BB=E5=8A=A0=E5=86=85?= =?UTF-8?q?=E5=AD=98=E9=80=83=E9=80=B8=E5=AD=A6=E4=B9=A0=E7=AC=94=E8=AE=B0?= =?UTF-8?q?=E4=B8=8E=E7=A4=BA=E4=BE=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 go/basics/MemoryEscape/main.go,演示返回局部变量指针和闭包引用两种典型逃逸场景 - 在 note/随手记/go.md 中补充内存逃逸的概念、影响、常见原因及逃逸分析命令 - 清理 note/随手记/网址.md 中的多余空行 --- go/basics/MemoryEscape/main.go | 25 ++++++++ note/随手记/go.md | 102 +++++++++++++++++++++++++++++++++ note/随手记/网址.md | 2 - 3 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 go/basics/MemoryEscape/main.go diff --git a/go/basics/MemoryEscape/main.go b/go/basics/MemoryEscape/main.go new file mode 100644 index 0000000..7bf398f --- /dev/null +++ b/go/basics/MemoryEscape/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" +) +type User struct { + Name string +} + +func NewUser() *User { + u := User{} + return &u +} + +func ClosureFunc() func() int { + a := 1 + return func() int { + return a + } +} +func main() { + fmt.Println(NewUser()) + cf:= ClosureFunc() + fmt.Println(cf()) +} diff --git a/note/随手记/go.md b/note/随手记/go.md index 8b13789..5d2288a 100644 --- a/note/随手记/go.md +++ b/note/随手记/go.md @@ -1 +1,103 @@ +## 内存逃逸 +### 什么是逃逸 + +在函数内部创建的对象或者变量,当函数的生命周期结束后仍然被引用或持有,则该对象或者变量被称之为逃逸对象 + +### 影响 + +- 对象分配到堆上,而不是栈上。 +- 堆内存由 Runtime 分配,由 GC 管理生命周期。 +- 相比栈分配,堆分配开销更大(需要 Runtime 参与)。 +- 增加 GC 扫描和回收压力,频繁逃逸可能影响程序性能。 +- 对象生命周期变长,不再随着函数返回立即释放,而是等待 GC 回收。 +- 如果多个 goroutine 共享该对象,可能产生数据竞争(Data Race),但这是共享对象导致的,不是逃逸本身导致的。 +- 逃逸本身不会导致内存泄漏,但如果堆对象长期被引用,GC 无法回收,就会造成 Go 意义上的内存泄漏。 + +### 常见原因 + +1. 返回局部变量的指针 + + 局部变量在函数返回后仍然需要使用,因此必须分配到堆上。 + + ```go + func NewUser() *User { + u := User{} + return &u + } + ``` + +2. 局部变量被闭包引用 + + 闭包可能在函数返回后仍然执行,因此引用的局部变量需要继续存活。 + + ```go + func f() func() int { + a := 1 + return func() int { + return a + } + } + ``` + +3. 被 goroutine 引用 + + goroutine 的执行时间可能超过当前函数的生命周期。 + + ```go + func f() { + a := 1 + go func() { + fmt.Println(a) + }() + } + ``` + +4. 作为 interface 参数或返回值(部分情况) + + 如果编译器无法证明对象不会逃逸,就会保守地分配到堆上。 + + ```go + func f() interface{} { + a := 10 + return a + } + ``` + + > 注意:并不是所有 interface 都会逃逸。 + +5. 存入生命周期更长的对象 + + 例如存入全局变量、堆对象或其他长期存在的数据结构。 + + ```go + var users []*User + + func f() { + u := User{} + users = append(users, &u) + } + ``` + + 这里 u 必须逃逸,因为 users 在函数返回后仍然存在。 + +6. 编译器无法证明不会逃逸 + + ```go + func g(x *User) + + func f() { + u := User{} + g(&u) + } + ``` + + 如果编译器无法确定 g 是否保存了 u 的地址,就会保守地让 u 逃逸到堆上。 + +### 逃逸分析命令 + +```bash +go build -gcflags -m main.go +``` + +出现`moved to heap`或者`escapes to heap`说明存在逃逸 diff --git a/note/随手记/网址.md b/note/随手记/网址.md index 2d364f8..c4b2514 100644 --- a/note/随手记/网址.md +++ b/note/随手记/网址.md @@ -1,5 +1,3 @@ [Typora onelight 主题](https://github.com/caolib/typora-onelight-theme?tab=readme-ov-file#4%E5%85%B3%E4%BA%8E%E5%AD%97%E4%BD%93) - - [Typora自定义主题](https://theme.typora.io/doc/zh/Write-Custom-Theme/)