Go 的 GC(垃圾回收)演变经历了几个版本的迭代和优化。下面简要介绍一下 Go 中 GC 的演变历程。
Go 1.0
在 Go 1.0 中,采用的是标记-清除(mark-and-sweep)算法进行垃圾回收。这种算法的主要特点是将内存分为已分配的和未分配的两部分,其中已分配的内存称为“堆”,未分配的内存称为“栈”。垃圾回收器通过标记堆中的存活对象,并清除未标记的对象来回收内存。
下面是一个使用 Go 1.0 版本进行垃圾回收的示例代码:
package main
func main() {
// 分配一个 1MB 的切片
data := make([]byte, 1<<20)
// 释放内存
data = nil
// 强制进行垃圾回收
runtime.GC()
}
在这个示例代码中,我们首先使用 make 函数分配了一个 1MB 大小的切片,然后将其赋值为 nil,即释放了内存。最后,我们使用 runtime.GC() 函数强制进行垃圾回收。
Go 1.5
在 Go 1.5 中,采用了并发标记-清除(concurrent mark-and-sweep)算法进行垃圾回收。这种算法的主要特点是在程序运行期间,垃圾回收器会在程序运行的同时进行标记和清除操作,从而避免了程序暂停的时间。
下面是一个使用 Go 1.5 版本进行垃圾回收的示例代码:
package main
import "time"
func main() {
// 分配一个 1MB 的切片
data := make([]byte, 1<<20)
// 释放内存
data = nil
// 等待 1 秒钟
time.Sleep(time.Second)
}
在这个示例代码中,我们首先使用 make 函数分配了一个 1MB 大小的切片,然后将其赋值为 nil,即释放了内存。最后,我们使用 time.Sleep 函数等待了 1 秒钟,这个过程中垃圾回收器会在后台进行并发的标记和清除操作。
Go 1.8
在 Go 1.8 中,采用了并发标记-整理(concurrent mark-and-sweep with compaction)算法进行垃圾回收。这种算法的主要特点是在进行标记和清除操作的同时,还会将存活对象移动到一起,从而使得堆中的内存连续,减少了内存碎片的产生。
下面是一个使用 Go 1.8 版本进行垃圾回收的示例代码:
package main
import (
"fmt"
"runtime"
)
func main() {
var m runtime.MemStats
// 获取垃圾回收器统计信息
runtime.ReadMemStats(&m)
fmt.Printf("Alloc = %v MiB\n", m.Alloc/1024/1024)
fmt.Printf("TotalAlloc = %v MiB\n", m.TotalAlloc/1024/1024)
fmt.Printf("Sys = %v MiB\n", m.Sys/1024/1024)
fmt.Printf("NumGC = %v\n", m.NumGC)
}
在这个示例代码中,我们首先使用 runtime.MemStats 结构体来存储垃圾回收器的统计信息。
然后,我们使用 runtime.ReadMemStats 函数获取垃圾回收器的统计信息,并输出了分配的内存大小、总分配的内存大小、系统分配的内存大小和垃圾回收次数。