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