Changes: - Renamed all 10 Golang files from English to Chinese names - Created 00-项目概述/项目概述.md with comprehensive project overview - Created 08-算法与数据结构/算法与数据结构学习指南.md with detailed learning guide - Created 12-面试技巧/面试准备进度.md with progress tracking - Added .obsidian configuration for better markdown editing - Updated Claude.MD with Chinese filename rule Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
4.3 KiB
4.3 KiB
Golang 内存模型和垃圾回收
问题
- Go 的内存分配策略是什么?
- Go 的垃圾回收(GC)是如何工作的?
- 什么是逃逸分析?
- Go 的内存泄漏常见场景有哪些?
- sync.Pool 的作用是什么?
- 如何优化 Go 程序的内存使用?
标准答案
1. 内存分配策略
堆和栈
栈(Stack):
- 存储局部变量
- 自动分配和释放
- 速度快
func foo() int {
x := 1 // 分配在栈上
return x
} // x 自动释放
堆(Heap):
- 存储动态分配的数据
- 需要 GC 回收
func foo() *int {
x := 1
return &x // x 逃逸到堆上
}
逃逸分析
定义:编译器分析变量的生命周期,决定分配在栈还是堆。
示例:
// ❌ 逃逸到堆
func foo() *int {
x := 1
return &x // x 的引用被返回,必须分配在堆上
}
// ✅ 不逃逸,分配在栈上
func bar() int {
x := 1
return x // x 的值被返回,可以分配在栈上
}
2. Go 的垃圾回收
GC 算法:三色标记法 + 并发标记清理
三色标记:
- 白色:未访问
- 灰色:已访问,但引用的对象未全部访问
- 黑色:已访问,引用的对象也全部访问
流程:
1. 根节点标记为灰色
2. 遍历引用对象,标记为灰色
3. 递归标记,直到所有可达对象为黑色
4. 清扫白色对象(垃圾)
GC 触发条件
// 环境变量
// GOGC:触发 GC 的堆增长率(默认 100)
// GOMEMLIMIT:触发 GC 的堆内存上限
// 示例
GOGC=100 // 堆增长 100% 时触发 GC
GOMEMLIMIT=8192 // 堆上限 8 GB
3. 常见内存泄漏场景
场景 1:Goroutine 泄漏
// ❌ 泄漏
func leak() {
for i := 0; i < 100; i++ {
go func() {
time.Sleep(time.Hour) // 永不退出
}()
}
}
解决:
func noLeak() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for i := 0; i < 100; i++ {
go func(ctx context.Context) {
select {
case <-ctx.Done():
return
case <-time.After(time.Hour):
}
}(ctx)
}
}
场景 2:切片引用
// ❌ 泄漏
func leak() {
for i := 0; i < 1000; i++ {
s := make([]int, 1024)
_ = append(s, i)
// s 被全局变量引用,无法 GC
}
}
4. sync.Pool
作用
对象池:复用对象,减少 GC 压力
示例
var pool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func process() {
// 从池中获取
buf := pool.Get().([]byte)
// 使用
// ...
// 归还到池中
pool.Put(buf)
}
适用场景
- 频繁创建的对象([]byte、Buffer)
- 临时对象(减少 GC)
- 对象创建成本高
5. 内存优化技巧
1. 预分配切片容量
// ❌ 多次扩容
s := []int{}
for i := 0; i < 1000; i++ {
s = append(s, i) // 多次扩容和拷贝
}
// ✅ 预分配
s := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
s = append(s, i) // 无扩容
}
2. 使用 strings.Builder
// ❌ 字符串拼接(内存拷贝)
s := ""
for i := 0; i < 1000; i++ {
s += fmt.Sprintf("%d", i) // 每次都创建新字符串
}
// ✅ strings.Builder
var b strings.Builder
for i := 0; i < 1000; i++ {
b.WriteString(strconv.Itoa(i))
}
s := b.String() // 只分配一次
3. 避免不必要的指针
// ❌ 使用指针(增加 GC 扫描时间)
type User struct {
Name *string
}
// ✅ 使用值类型
type User struct {
Name string
}
6. 阿里 P7 加分项
深度理解:
- 理解 Go 的内存分配策略(栈、堆、逃逸分析)
- 理解 Go GC 的三色标记法
- 理解 GC 的触发条件和调优参数
实战经验:
- 有使用 pprof 进行内存分析的经验
- 有优化内存使用的经验(减少 GC、复用对象)
- 有处理内存泄漏的经验
性能优化:
- 理解如何减少内存分配(sync.Pool、预分配)
- 理解如何减少 GC 压力(避免指针、复用对象)
- 理解如何使用 pprof 进行性能分析