Files
interview/questions/13-Golang语言/内存模型和垃圾回收.md
yasinshaw 7f3ab362b3 feat: rename Golang files to Chinese and supplement root files
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>
2026-03-01 00:33:32 +08:00

4.3 KiB
Raw Permalink Blame History

Golang 内存模型和垃圾回收

问题

  1. Go 的内存分配策略是什么?
  2. Go 的垃圾回收GC是如何工作的
  3. 什么是逃逸分析?
  4. Go 的内存泄漏常见场景有哪些?
  5. sync.Pool 的作用是什么?
  6. 如何优化 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. 常见内存泄漏场景

场景 1Goroutine 泄漏

// ❌ 泄漏
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 进行性能分析