如何理解golang的垃圾回收机制 | 青训营

73 阅读5分钟

垃圾回收(GC)是一种自动内存管理的机制。它的作用是回收程序分配但不再使用的内存空间,从而避免内存泄漏和浪费。golang是一门支持自动垃圾回收的编程语言,它的运行时库包含了一个垃圾回收器,负责定期扫描程序的堆(heap),寻找不再可达的对象,并将它们释放。

为什么需要垃圾回收

在编程过程中,我们经常需要创建变量、对象、数组等数据结构,这些数据结构都需要占用一定的内存空间。如果我们不及时释放不再需要的内存空间,就会导致内存占用越来越多,甚至造成程序崩溃。因此,我们需要有一种机制来管理内存的分配和释放,确保程序能够高效地运行。

有些编程语言,如C/C++,要求程序员显式地管理内存,使用malloc、free、new、delete等函数或操作符来分配和释放内存。这种方式虽然可以让程序员对内存有更多的控制,但也增加了程序员的负担和出错的风险。例如,如果忘记释放已分配的内存,就会造成内存泄漏;如果释放了还在使用的内存,就会造成野指针;如果重复释放同一块内存,就会造成双重释放。

golang则采用了另一种方式,即自动垃圾回收。这种方式不需要程序员关心内存的分配和释放,而是交由golang的运行时库来完成。这样可以减少程序员的工作量和错误,让程序员专注于业务逻辑和性能优化。

golang的垃圾回收原理

golang的垃圾回收器是基于三色标记清除算法实现的。这种算法将堆中的对象分为三种颜色:白色、灰色和黑色。其中:

  • 白色表示对象不可达,即没有任何指针指向该对象,或者该对象被其他白色对象引用。这些对象是垃圾回收器的目标,将被回收。
  • 灰色表示对象可达,但还没有扫描其引用的其他对象。这些对象是垃圾回收器的工作集合,需要进一步处理。
  • 黑色表示对象可达,并且已经扫描其引用的其他对象。这些对象是垃圾回收器的安全区域,不需要再处理。

垃圾回收器的工作流程如下:

  1. 初始状态下,所有对象都是白色的。
  2. 垃圾回收器从根集合(root set)开始扫描,根集合包括全局变量、栈变量、寄存器等可以直接访问到堆上对象的指针。将根集合中指向堆上对象的指针所指向的对象标记为灰色,并加入灰色集合(gray set)。
  3. 垃圾回收器从灰色集合中取出一个灰色对象,并扫描该对象引用的其他对象。如果发现白色对象,则将其标记为灰色,并加入灰色集合;如果发现灰色或黑色对象,则不做处理。将该灰色对象标记为黑色,并从灰色集合中移除。
  4. 重复步骤3,直到灰色集合为空。此时,所有可达的对象都被标记为黑色,不可达的对象都保持为白色。
  5. 垃圾回收器扫描堆上的所有对象,将白色对象回收,释放其占用的内存空间。

golang的垃圾回收特点

golang的垃圾回收器有以下几个特点:

  • 并发。golang的垃圾回收器是在一个单独的后台线程中运行的,与程序的主线程并发执行。这样可以减少垃圾回收对程序性能的影响,避免长时间的停顿。
  • 低延迟。golang的垃圾回收器使用了一些优化技术,如写屏障、混合扫描、非均匀分配等,来降低垃圾回收的延迟。在golang 1.19版本中,垃圾回收的平均停顿时间为0.5毫秒,最大停顿时间为2毫秒。
  • 可调节。golang的垃圾回收器可以通过一些环境变量来调节其行为和参数。例如,GOGC变量可以设置垃圾回收的触发阈值,GODEBUG变量可以设置垃圾回收的日志和统计信息,GOMAXPROCS变量可以设置垃圾回收使用的最大CPU核数等。

golang的垃圾回收优化

虽然golang的垃圾回收器已经很高效了,但是我们仍然可以通过一些方法来优化我们的程序,以减少垃圾回收的开销和频率。以下是一些常用的优化技巧:

  • 减少内存分配。尽量使用栈上分配而不是堆上分配,尽量复用已分配的内存而不是重新分配新内存,尽量避免使用反射、空接口、动态数组等会导致额外内存分配的特性。
  • 减少指针逃逸。指针逃逸是指一个指针从其定义的作用域中逃逸出去,例如返回给调用者、赋值给全局变量、传递给其他函数等。指针逃逸会导致其指向的对象被分配在堆上而不是栈上,从而增加了垃圾回收器的工作量。我们可以使用go build -gcflags "-m"命令来查看哪些指针发生了逃逸,并尝试避免它们。
  • 减少内存碎片。内存碎片是指内存空间被不连续地占用和释放,导致剩余的可用空间变得零散和不足。内存碎片会影响内存分配的效率和速度,甚至导致内存不足。我们可以通过使用固定大小的数据结构、合理地组织数据结构的字段顺序、使用sync.Pool等方式来减少内存碎片。