8.6 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-06-07 14:30 |
垃圾回收面试题 ♻️
概述
本文件涵盖 Go GC(Garbage Collection)的 14 道高频面试题,涉及 GC 算法、三色标记、写屏障、STW、调优等核心概念。GC 是 Go 运行时的重要组成部分,理解它对于编写高性能程序至关重要。
关联笔记
- hzh/GolangStar/Go语言原理/垃圾回收 — GC 源码级分析
- hzh/GolangStar/Go语言原理/内存管理 — 分配器与 GC 的关系
- hzh/GolangStar/Go面试题库/内存管理面试题 — 内存泄漏场景
正文
Q1:常见的 GC 实现方式有哪些? 🟡中等
参考答案
| 算法 | 特点 |
|---|---|
| 标记清扫 | 从根对象出发标记存活对象,清扫不可达对象 |
| 标记整理 | 在标记过程中整理对象到连续内存,解决碎片 |
| 增量式 | 将标记和清扫分批执行,降低停顿时间 |
| 分代式 | 按存活时间分年轻代/老年代/永久代 |
| 引用计数 | 引用归零时立即回收,但有循环引用问题 |
[!note] 📝 核心考点 Go 采用的是:无分代 + 不整理 + 并发 + 三色标记清扫。面试时可以对比 Java 的分代 GC 来说明 Go 的设计取舍。
Q2:Go 的 GC 使用的是什么算法? 🟢简单
参考答案
无分代、不整理、并发的三色标记清扫算法。
[!tip] 💡 面试技巧 三个关键词要记住:"无分代"(不做年龄分类)、"不整理"(不移动对象)、"并发"(和用户代码同时运行)。
Q3:三色标记法是什么? 🟡中等
[!question] ❓ 思考一下 如果 GC 扫描和程序运行同时进行,可能会出现什么情况导致错误回收对象?
参考答案
三色标记通过三种颜色区分对象状态:
| 颜色 | 含义 | 最终命运 |
|---|---|---|
| 白色 | 未被访问 | 被回收(如果是垃圾) |
| 灰色 | 已被访问但未扫描完子节点 | 待处理队列 |
| 黑色 | 已被访问且所有子节点已扫描完 | 确认存活 |
标记流程:
graph LR
A[全部白色] --> B[根对象标为灰色]
B --> C{灰色队列空?}
C -->|否| D[取出灰色对象]
D --> E[扫描其引用的对象]
E --> F{引用是白色?}
F -->|是| G[标为灰色]
F -->|否| H[不变色]
E --> I[当前对象标为黑色]
I --> C
C -->|是| J[标记完成: 黑色存活, 白色回收]
style A fill:#FFFFFF
style B fill:#FF9800,color:#fff
style I fill:#4CAF50,color:#fff
style J fill:#F44336,color:#fff
[!info] 🔗 延伸阅读
- hzh/GolangStar/Go语言原理/垃圾回收 — 完整的 GC 流程图解
Q4:Go 语言 GC 的根对象是什么? 🟢简单
参考答案
根对象(Root Set)包括三类:
| 类型 | 说明 |
|---|---|
| 全局变量 | 编译期确定的、贯穿程序生命周期的变量 |
| 执行栈 | 每个 goroutine 的栈上变量及指向堆的指针 |
| 寄存器 | 可能表示堆内存指针的寄存器值 |
Q5:STW 是什么意思? 🟢简单
参考答案
STW = Stop the World,指用户代码被完全停止运行的阶段。STW 越长,对延迟的影响越大。
早期 Go 的 STW 长达几百毫秒,现代 Go 已优化到微秒级。
[!warning] ⚠️ 高频陷阱 虽然大部分 GC 工作是并发的,但仍有两个 STW 阶段:MarkTermination(标记终止)和 SweepTermination(清扫终止),这两个阶段的 STW 时间需要严格控制。
Q6:并发标记清除法的难点? 🟡中等
[!question] ❓ 思考一下 如果 GC 在标记黑色对象 C 的子节点时,用户程序并发地将 C 的引用从白色对象 B 改到了另一个白色对象 D,会发生什么?
参考答案
核心难点:如何在用户程序并发修改引用时保证 GC 正确性。
两大问题:
| 问题 | 描述 |
|---|---|
| 对象消失 | 黑色对象到白色对象的引用被删除,同时灰色到白色的引用也被删除,白色对象被错误回收 |
| 新对象处理 | 标记期间新分配的对象如何着色?标白可能误回收,标黑可能造成浮动垃圾 |
[!warning] ⚠️ 高频陷阱 "对象消失"是并发 GC 最经典的问题。黑色对象不再被重新扫描,如果它与白色对象之间的唯一路径被切断,这个白色对象就会被误回收。
[!info] 🔗 延伸阅读
- hzh/GolangStar/Go语言原理/垃圾回收 — 对象消失问题的详细时序分析
Q7:Go 如何解决并发标记时的引用修改问题? 🟡中等
参考答案
通过写屏障技术和三色不变性维护:
混合写屏障(Go 1.8+):
| 操作 | 处理方式 |
|---|---|
| 新建引用 | 将目标对象着为灰色 |
| 删除引用 | 将被删对象标为灰色 |
| 栈上新建对象 | 默认标记为黑色(无需重扫) |
弱三色不变性:允许黑色对象指向白色对象,但要保证从白色对象出发存在全灰色路径可达根对象。
[!tip] 💡 面试技巧 "写屏障就像在每次指针赋值时安了一个监控摄像头——只要有人改了引用关系,就立刻把相关对象重新检查一遍,确保不会漏掉任何可达对象。"
Q8:什么是写屏障、混合写屏障? 🟡中等
参考答案
写屏障是在编译器指针赋值操作中插入的额外短指令:
| 类型 | 时机 | 处理方式 | 局限 |
|---|---|---|---|
| 插入写屏障 (Dijkstra) | 建立新引用时 | 目标标灰 | 删除引用时无保护 |
| 删除写屏障 (Yuasa) | 删除引用时 | 原对象标灰 | 新建引用时无保护 |
| 混合写屏障 (Go 1.8+) | 两者都处理 | 建立→目标标灰;删除→原对象标灰 | 需特殊处理栈操作 |
[!note] 📝 核心考点 混合写屏障的关键优化:标记阶段创建的栈上新对象默认标记为黑色,因此不需要 STW 重扫栈——因为栈本身就是黑色的,它指向的对象必然是可达的。
Q9:Go GC 的流程? 🟡中等
参考答案
| 阶段 | 说明 | 状态 |
|---|---|---|
| SweepTermination | 清扫终止,为下一轮做准备 | STW |
| Mark | 扫描标记,与用户代码并发 | 并发 |
| MarkTermination | 标记终止,保证周期内完成 | STW |
| GC off | 内存清扫,归还可回收内存 | 并发 |
| GC off | 内存归还,释放多余内存给 OS | 并发 |
Q10:GC 触发的时机? 🟢简单
参考答案
| 触发方式 | 条件 |
|---|---|
| 主动触发 | runtime.GC() 阻塞式等待完成 |
| 被动触发 | ① 超过 2 分钟没有 GC → 强制触发 |
| ② 内存增长达到阈值(GOGC,默认 100%) |
第一次 GC 的触发临界值是 4MB。
[!tip] 💡 面试技巧 "GOGC=100 意味着当当前堆大小超过上次 GC 后标记堆大小的 100% 时触发。可以通过
debug.SetGCPercent(500)调整。"
Q11:GC 关注的指标? 🟢简单
参考答案
| 指标 | 说明 |
|---|---|
| CPU 利用率 | GC 占用多少 CPU |
| GC 停顿时间 | STW 和 Mark Assist 造成的停顿 |
| GC 停顿频率 | 多久触发一次 GC |
| GC 可扩展性 | 堆变大时性能如何衰减 |
Q12:有了 GC 为什么还会发生内存泄漏? 🟢简单
参考答案
GC 只能回收不可达的对象。如果对象仍然被根对象引用但业务上已不再使用,就会泄漏:
- 被根对象持有:局部变量被赋值到全局 map 中
- goroutine 泄漏:goroutine 不能正常退出,持有的所有变量都无法回收
Q13:Go 的 GC 如何调优? 🟡中等
参考答案
| 手段 | 说明 |
|---|---|
| 减少内存分配 | 使用 sync.Pool 复用对象 |
| 调整 GOGC | 增大 GOGC 值减少 GC 频率 |
| 提高分配速度 | 合理化内存分配,提高 CPU 利用率 |
Q14:如何观察 Go GC? 🟡中等
参考答案
四种方式:
| 方式 | 适用场景 |
|---|---|
GODEBUG=gctrace=1 |
输出详细的 GC 日志 |
go tool trace |
可视化展示 GC 和执行信息 |
debug.ReadGCStats |
代码内监控 GC 状态 |
runtime.ReadMemStats |
监控内存相关指标 |
[!info] 🔗 延伸阅读
- hzh/GolangStar/Go语言原理/垃圾回收 — gctrace 日志的详细字段解析