卡码笔记-最强八股文
首页
计算机基础
C++
Java
Go
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
首页
计算机基础
C++
Java
Go
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
  • Go八股

    • Go基础
    • Go并发
    • Go web
    • Go 垃圾回收

分布式部署的仿微信项目KamaChat (opens new window)是一个进阶的Go的项目,基本覆盖了GO相关技术栈的全部内容,做完不仅可以在简历上添加一个项目,还可以活学活用最强八股文里Go的全部八股内容:web开发、Go并发、GC调优,通信、分布式、数据层设计等等。

# Go垃圾回收

# 常见的 GC 实现方式有哪些? (考点:GC 实现方式)【简单】

常见的GC实现方式主要有6种:

  1. 标记-清理:首先标记所有可达对象,然后清理不可达对象。可能会产生内存碎片
  2. 复制:将内存分为两半,每次只使用其中的一半,当内存使用完事,将其中存活的对象复制到另一半中,然后清理使用的那一半内存。适用于对象生命周期短的情况,因为浪费内存少
  3. 标记-整理:标记所有可达的对象,然后将它们移动至内存的一端,清理边界外的内存。解决了标记-清理法的内存碎片问题
  4. 增量:将GC分散到程序执行过程的多个小步骤,减少GC停顿时间,提高用户体验
  5. 分代:根据对象的生命周期的不同将内存分为不同的代,新分配的内存为新生代,存活达到一定时间的对象为老年代。新生代频繁回收,老年代较少回收
  6. 并发:GC和程序的其他部分(如执行代码的线程)并发运行,但需要同步机制的支持

Go语言采用并发的标记-清理和 标记-整理法,它在程序运行时并发地执行GC,尽量减少对程序性能的影响

# Go垃圾回收(GC)的触发条件是什么?(考点:垃圾回收触发机制)【简单】

  1. 堆内存分配:当程序分配的堆内存达到一定阈值时,GC会被触发以释放不再使用的对象,从而减少内存使用。
  2. 空闲内存低:即使堆内存使用率不高,如果可用内存(空闲内存)低于某个阈值,也可能触发GC。
  3. 显式调用:虽然不常见,但可以通过调用runtime.GC()来强制执行GC。
  4. 时间间隔:Go的GC还有一个定时器,即使当前内存分配不多,超过一定时间间隔后,也会触发GC。
  5. 内存泄露检测:长时间运行的程序如果检测到可能的内存泄露,也会触发GC尝试回收内存。

Go语言的GC设计目标是隐藏GC的开销,让程序员不需要关心内存管理,所以大部分情况下GC是自动和透明的。

# Go语言中的GC流程。(考点:GC流程)【简单】

Go语言中的垃圾回收(GC)流程是并发执行的,以减少对程序性能的影响。GC的主要流程大致如下:

1.标记(Mark):找出所有从根可达的对象。这个过程会中断程序的执行(stop-the-world),但这个停顿非常短暂。

2.标记后处理(Mark Termination):处理并发标记过程中遗留下来的工作。

3.清扫(Sweep):回收未被标记(不可达)的对象所占用的内存。在这个阶段,内存会被重新整理,减少内存碎片。

4.并发标记(Concurrent Mark):并发标记阶段,GC会与程序同时运行,遍历所有标记的对象,确保没有被遗漏。

5.并发清扫(Concurrent Sweep): 并发清扫阶段,GC会与程序同时运行,实际回收不可达对象所占用的内存。

6.并发GC辅助工作:进行一些GC的辅助工作,如处理GC堆的后台工作。

# go的垃圾回收机制了解吗?

Go1.3之前采用标记清除法, Go1.3之后采用三色标记法,Go1.8采用三色标记法+混合写屏障。

  1. 标记清除法

初始版本的Go语言使用了一个基于标记-清扫(Mark-Sweep)算法的垃圾回收器。

  • 在标记清除算法中,首先从根对象(如全局变量、栈中的引用等)出发,标记所有可达对象。这一过程通常使用深度优先搜索或广度优先搜索进行。标记的方式通常是将对象的标记位从未标记改为已标记。所有的可达对象都被标记为“活动”或“存活”。
  • 在清扫阶段,遍历整个堆内存,将未被标记的对象视为垃圾,即不再被引用。所有未被标记的对象都将被回收,它们的内存将被释放,以便后续的内存分配。
  • 标记清除算法执行完清扫阶段后,可能会产生内存碎片,即一些被回收的内存空间可能是不连续的。为了解决这个问题,一些实现中可能会进行内存碎片整理。
  • 标记清除算法的主要优势是能够回收不再使用的内存,但它也有一些缺点。其中一个主要的缺点是清扫阶段可能会引起一定程度的停顿,因为在这个阶段需要遍历整个堆内存。另外,由于标记清除算法只关注“存活”和“垃圾”两种状态,不涉及内存分配的具体位置,可能导致内存碎片的产生。
  1. 三色标记法
  • 三色标记:将对象分为三种颜色:白色、灰色、和黑色。初始时,所有对象都被标记为白色,表示它们都是未被访问的垃圾对象。
  • 根搜索:垃圾回收从根对象开始搜索,根对象包括全局变量、栈上的对象以及其他一些持有对象引用的地方。所有根对象被标记为灰色,表示它们是待处理的对象。
  • 标记阶段:从灰色对象开始,垃圾回收器遍历对象的引用关系,将其引用的对象标记为灰色,然后将该对象标记为黑色。这个过程一直进行,直到所有可达对象都被标记为黑色。
  • 并发标记:在标记阶段,垃圾回收器采用并发标记的方式,与程序的执行同时进行。这意味着程序的执行不会因为垃圾回收而停顿,从而减小了对程序性能的影响。
  • 清扫阶段:在标记完成后,垃圾回收器会扫描堆中的所有对象,将未被标记的对象回收(释放其内存)。这些未被标记的对象被认为是不可达的垃圾。
  • 内存返还:垃圾回收完成后,系统中的内存得以回收并用于新的对象分配。
  • GC触发:垃圾回收的触发条件通常是在分配新对象时,如果达到一定的内存分配阈值,就会触发垃圾回收。另外,一些特定的事件(如系统调用、网络阻塞等)也可能触发垃圾回收。
  1. 三色标记法+混合写屏障

这种方法有一个缺陷,如果对象的引用被用户修改了,那么之前的标记就无效了。因此Go采用了写屏障技术,当对象新增或者更新会将其着色为灰色。

一次完整的GC分为四个阶段:

  1. 准备标记(需要STW),开启写屏障。
  2. 开始标记
  3. 标记结束(STW),关闭写屏障
  4. 清理(并发)

基于插入写屏障和删除写屏障在结束时需要STW来重新扫描栈,带来性能瓶颈。混合写屏障分为以下四步:

  1. GC开始时,将栈上的全部对象标记为黑色(不需要二次扫描,无需STW);
  2. GC期间,任何栈上创建的新对象均为黑色
  3. 被删除引用的对象标记为灰色
  4. 被添加引用的对象标记为灰色

总而言之就是确保黑色对象不能引用白色对象,这个改进直接使得GC时间从 2s降低到2us。

# GC如何调优

通过 go tool pprof 和 go tool trace 等工具

  • 控制内存分配的速度,限制 Goroutine 的数量,从而提高赋值器对 CPU的利用率。

  • 减少并复用内存,例如使用 sync.Pool 来复用需要频繁创建临时对象,例如提前分配足够的内存来降低多余的拷贝。

  • 需要时,增大 GOGC 的值,降低 GC 的运行频率。

# GMP

GMP 指的是 Go 的运行时系统(Runtime)中的三个关键组件:Goroutine、M(Machine)、P(Processor)。

  1. Goroutine:
    • Goroutine 是 Go 语言中的轻量级线程,它由 Go 运行时管理。Goroutines 是并发执行的基本单位,相比于传统的线程,它们更轻量,消耗更少的资源,并由运行时系统调度。在 Go 中,你可以创建成千上万个 Goroutine,并且它们可以非常高效地运行。
  2. M(Machine):
    • M 表示调度器的线程,它负责将 Goroutines 映射到真正的操作系统线程上。在运行时系统中,有一个全局的 M 列表,每个 M 负责调度 Goroutines。当一个 Goroutine 需要执行时,它会被分配给一个 M,并在该 M 的线程上运行。M 的数量可以根据系统的负载动态调整。
  3. P(Processor):
    • P 表示处理器,它是用于执行 Goroutines 的上下文。P 可以看作是调度上下文,它保存了 Goroutines 的执行状态、调度队列等信息。P 的数量也是可以动态调整的,它不是直接与物理处理器核心对应的,而是与运行时系统中的 Goroutines 数目和负载情况有关。

GMP 模型的工作原理如下:

  • 当一个 Goroutine 被创建时,它会被放入一个 P 的本地队列。
  • 当 P 的本地队列满了,或者某个 Goroutine 长时间没有被调度执行时,P 会尝试从全局队列中获取 Goroutine。
  • 如果全局队列也为空,P 会从其他 P 的本地队列中偷取一些 Goroutines,以保证尽可能多地利用所有的处理器。
  • M 的数量决定了同时并发执行的 Goroutine 数目。如果某个 M 阻塞(比如在系统调用中),它的工作会被其他 M 接管。

# Go 中的内存逃逸现象是什么?

内存逃逸(Memory Escape)是指一个变量在函数内部创建,但在函数结束后仍然被其他部分引用,导致变量的生命周期超出了函数的范围,从而使得该变量的内存需要在堆上分配而不是在栈上分配。

存逃逸的情况可能发生在以下几种情况:

1、 当在函数内部创建一个局部变量,然后返回该变量的指针,而该指针被函数外部的代码引用时,这个局部变量会发生内存逃逸。

func createPointer() *int {
    x := 42
    return &x // x 的内存逃逸
}
1
2
3
4

2、当将局部变量通过 channel 或 goroutine 传递给其他部分,而这些部分可能在原始函数退出后访问这个变量时,也会导致内存逃逸。

func sendData(ch chan<- *int) {
    x := 42
    ch <- &x // x 的内存逃逸
}
1
2
3
4

3、如果在函数内部使用 new 或 make 分配的变量,即使返回的是指针,但这个指针可能被外部持有,从而导致变量在堆上分配而不是在栈上分配。

func createWithNew() *int {
    x := new(int) // x 的内存逃逸
    return x
}

1
2
3
4
5
Last Updated: 3/19/2026, 4:49:23 PM

← Go web

评论

验证登录状态...

侧边栏
夜间
卡码简历
代码随想录
卡码投递表🔥
2026群
添加客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记
鼓励/支持/赞赏Carl
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪