go 内存管理详解 | 青训营笔记

121 阅读4分钟

Go 语言内存管理详解

Go 语言的内存管理由 Go 运行时系统 (runtime) 负责,它采用了垃圾回收 (GC) 技术来自动管理内存。这篇笔记将从多个角度理解和分享 Go 语言的内存管理。

内存分配

在 Go 中,内存分配是由 runtime 管理的。runtime 包中提供了一些函数来分配内存,例如 make 和 new。make 用于分配并初始化 slice、map 和 channel 等数据结构,new 用于分配未初始化的值的指针。内存分配的过程是由 runtime 做出决策,包括何时分配、如何分配、何时释放等等。

垃圾回收

Go 语言采用的是基于标记-清除 (mark-and-sweep) 算法的垃圾回收机制。垃圾回收器会定期扫描堆中的对象,标记不再被引用的对象,并清除它们。当一个对象被标记为不再被引用时,它将被垃圾回收器清除。

垃圾回收会对程序性能产生一定的影响,因为它需要扫描整个堆来查找不再被引用的对象。为了减少垃圾回收的开销,Go 语言的 GC 采用了一些优化技术,例如分代 GC 和并发 GC。

分代 GC

分代 GC 是指将对象按照其生命周期划分为不同的代,不同代的对象有不同的生命周期。一般来说,新创建的对象比较容易被回收,而存活时间较长的对象则比较难以回收。因此,分代 GC 将堆划分为年轻代和老年代,采用不同的回收策略来处理不同代的对象。

年轻代采用的是复制算法,将堆划分为两个相等的区域,称为 From 区和 To 区。新创建的对象会被分配到 From 区,当 From 区满时,会触发垃圾回收,将还存活的对象复制到 To 区。复制完成后,From 区的所有对象都被清除,然后将 From 区和 To 区交换,使得 To 区成为新的 From 区,From 区成为新的 To 区。

老年代采用的是标记-清除算法。当一个对象在年轻代经历了多次垃圾回收后仍然存活,它将被移动到老年代。老年代中的对象较为稳定,不需要进行复制,因此采用标记-清除算法来回收内存。

  1. 并发 GC

并发 GC 是指在垃圾回收过程中,程序的其他部分仍然可以运行。在 Go 语言中,垃圾回收是在单独的 GC 线程中运行的,程序的其他线程可以继续运行。在垃圾回收过程中,GC 线程会挂起所有程序线程,然后扫描堆中的对象,标记不再被引用的对象,并清除它们。完成后,GC 线程会恢复所有程序线程的运行。

为了减少 GC 的停顿时间,Go 语言的 GC 采用了一些优化技术,例如增量标记和并发标记。增量标记是指在 GC 过程中,将标记过程分成多个阶段,在每个阶段中让程序线程执行一小段时间,然后让 GC 线程执行一小段时间,交替执行。并发标记是指在 GC 过程中,让程序线程和 GC 线程同时运行,GC 线程会在后台完成垃圾回收,而程序线程可以继续运行。

内存泄漏

内存泄漏是指程序中的某个对象已经不再被使用,但是仍然占用着内存。在 Go 语言中,内存泄漏通常是因为程序中有一个长期保持引用的对象,导致垃圾回收器无法清除该对象。为了避免内存泄漏,需要注意避免长期保持引用,以便让垃圾回收器及时清除不再被使用的对象。

内存分配和回收的性能

内存分配和回收的性能对程序的性能有很大的影响。为了提高内存分配和回收的性能,Go 语言的 runtime 采用了一些优化技术,例如对象池、复用 goroutine 等。对象池是指事先分配一些对象并缓存起来,当需要分配对象时,可以从对象池中获取对象,而不是每次都分配新的对象。复用 goroutine 是指在执行完任务后,将 goroutine 放回池中,以便下次执行任务时可以直接从池中获取 goroutine。

总结

Go 语言的内存管理由 runtime 负责,采用垃圾回收技术来自动管理内存。内存分配和回收的性能对程序的性能有很大的影响,为了提高性能,Go 语言的 runtime 采用了一些优化技术,例如分代 GC、并发 GC、对象池、复用 goroutine 等。为了避免内存泄漏,需要注意避免长期