docs(go): 添加内存逃逸学习笔记与示例代码
- 新增 go/basics/MemoryEscape/main.go,演示返回局部变量指针和闭包引用两种典型逃逸场景 - 在 note/随手记/go.md 中补充内存逃逸的概念、影响、常见原因及逃逸分析命令 - 清理 note/随手记/网址.md 中的多余空行
This commit is contained in:
parent
3819ae9d94
commit
b6063cb9f9
25
go/basics/MemoryEscape/main.go
Normal file
25
go/basics/MemoryEscape/main.go
Normal file
@ -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())
|
||||
}
|
||||
102
note/随手记/go.md
102
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`说明存在逃逸
|
||||
|
||||
@ -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/)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user