Files
cs-note/hzh/GolangStar/Go面试题库/垃圾回收面试题.md
T

266 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [go, golang, interview, gc-questions]
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 扫描和程序运行同时进行,可能会出现什么情况导致错误回收对象?
## 参考答案
三色标记通过三种颜色区分对象状态:
| 颜色 | 含义 | 最终命运 |
|------|------|---------|
| **白色** | 未被访问 | 被回收(如果是垃圾) |
| **灰色** | 已被访问但未扫描完子节点 | 待处理队列 |
| **黑色** | 已被访问且所有子节点已扫描完 | 确认存活 |
**标记流程:**
```mermaid
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 只能回收**不可达**的对象。如果对象仍然被根对象引用但业务上已不再使用,就会泄漏:
1. **被根对象持有**:局部变量被赋值到全局 map 中
2. **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 日志的详细字段解析
## 关联笔记
- [[hzh/GolangStar/Go语言原理/垃圾回收]]
- [[hzh/GolangStar/Go语言原理/内存管理]]
- [[hzh/GolangStar/Go面试题库/内存管理面试题]]