## Go 的垃圾回收
垃圾回收是释放未使用的内存空间的过程。换句话说,垃圾收集器查看哪些对象不在范围内并且无法再引用,并释放它们消耗的内存空间。此过程在 Go 程序运行时(而不是在程序执行之前或之后)以并行方式发生。 Go 垃圾收集器实现的文档指出以下内容:
_The GC runs concurrently with mutator threads, is type accurate (aka precise), allows multiple GC thread to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is non-generational and non-compacting. Allocation is done using size segregated per P allocation areas to minimize fragmentation while eliminating locks in the common case._
_垃圾回收是和 goroutine 同时运行的,它是类型精确的,而且多个垃圾回收线程可以并行运行。它是一种使用了写屏障的并发标记清除的垃圾回收方式。它是非分代和非压缩的。使用按 P 分配区域隔离的大小来完成分配,以最小化碎片,同时消除常见情况下的锁。_
这里面有很多术语,我一会儿会解释。但是首先,我会为你展示一种查看垃圾回收过程的参数的方式。
幸运的是,Go 标准库提供了方法,允许你去学习垃圾回收器的操作以及了解更多关于垃圾回收器在背后所做的事情。相关的代码保存在了`gColl.go`中,它有三个部分。
`gColl.go`的第一部分代码段如下所示:
```Go
package main
import (
"fmt"
"runtime"
"time"
)
func printStats(mem runtime.MemStats) {
runtime.ReadMemStats(&mem)
fmt.Println("mem.Alloc:", mem.Alloc)
fmt.Println("mem.TotalAlloc:", mem.TotalAlloc)
fmt.Println("mem.HeapAlloc:", mem.HeapAlloc)
fmt.Println("mem.NumGC:", mem.NumGC)
fmt.Println("-----")
}
```
注意到每次你都需要获取更多最近的垃圾回收统计信息,你会需要调用`runtime.ReadMemStats()`方法。`printStats()`方法的目的是去避免每次要写相同代码。
第二部分代码如下:
```Go
func main() {
var mem runtime.MemStats
printStats(mem)
for i := 0; i < 10; i++ {
s := make([]byte, 50000000)
if s == nil {
fmt.Println("Operation failed!")
printStats(mem)
```
for 循环里创建一堆大的 Go **slices**,目的是为了进行大量内存分配来触发垃圾回收。
最后一部分是接下来`gColl.go`的代码,用 Go slices 进行了更多的内存分配。
```Go
for i := 0; i < 10; i++ {
s := make([]byte, 100000000)
if s == nil {
fmt.Println("Operation failed!")
time.Sleep(5 * time.Second)
}
}
printStats(mem)
}
```
在 macOS Mojave 上面`gColl.go`的输出如下:
```shell
$ go run gColl.go
mem.Alloc: 66024
mem.TotalAlloc: 66024
mem.HeapAlloc: 66024
mem.NumGC: 0
-----
mem.Alloc: 50078496
mem.TotalAlloc: 500117056
mem.HeapAlloc: 50078496
mem.NumGC: 10
-----
mem.Alloc: 76712
mem.TotalAlloc: 1500199904
mem.HeapAlloc: 76712
mem.NumGC: 20
-----
```
尽管你不会一直检查 Go 垃圾收集器的操作,但是从长远来看,能够观察它在运行缓慢的应用程序上的运行方式可以节省大量时间。我很确定,花点时间去整体学习了解垃圾回收,尤其是了解 go 垃圾回收器的工作方式,将会很值得。
这里有一个技巧可以让你得到更多关于 go 垃圾收集器操作的细节,使用下面这个命令:
```shell
$ GODEBUG=gctrace=1 go run gColl.go
```
所以,如果你在任何 go run 命令前面加上`GODEBUG=gctrace=1`,go 就会去打印关于垃圾回收操作的一些分析数据。生成的数据是如下的形式:
```shell
gc 4 @0.025s 0%: 0.002+0.065+0.018 ms clock,
0.021+0.040/0.057/0.003+0.14 ms cpu, 47->47->0 MB, 48 MB goal, 8 P
gc 17 @30.103s 0%: 0.004+0.080+0.019 ms clock,
0.033+0/0.076/0.071+0.15 ms cpu, 95->95->0 MB, 96 MB goal, 8 P
```
前面的输出告诉我们有关垃圾回收过程中堆大小的更多信息。因此,让我们以`47 -> 47 -> 0 MB`为例。第一个数字是垃圾收集器即将运行时的堆大小。第二个值是垃圾收集器结束其操作时的堆大小。最后一个值是活动堆的大小。
- 介绍
- 1 Go与操作系统
- 01.1 Go的历史
- 01.2 Go的未来
- 01.3 Go的优点
- 01.3.1 Go是完美的么
- 01.3.2 什么是预处理器
- 01.3.3 godoc
- 01.4 编译Go代码
- 2 理解 Go 的内部构造
- Go 编译器
- Go 的垃圾回收
- 三色算法
- 有关 Go 垃圾收集器操作的更多信息
- Maps, silces 与 Go 垃圾回收器
- Unsafe code
- 有关 unsafe 包
- 另一个 usafe 包的例子
- 从 Go 调用 C 代码
- 在同一文件用 Go 调用 C 代码
- 在单独的文件用 Go 调用 C 代码
- 从 C 调用 Go 代码
- Go 包
- C 代码
- defer 关键字
- 用 defer 打印日志
- Panic 和 Recover
- 单独使用 Panic 函数
- 两个好用的 UNIX 工具
- strace
- dtrace
- 配置 Go 开发环境
- go env 命令
- Go 汇编器
- 节点树
- 进一步了解 Go 构建
- 创建 WebAssembly 代码
- 对 Webassembly 的简单介绍
- 为什么 WebAssembly 很重要
- Go 与 WebAssembly
- 示例
- 使用创建好的 WebAssembly 代码
- Go 编码风格建议
- 练习和相关链接
- 本章小结
- 3 Go基本数据类型
- 03.1 Go循环
- 03.1.1 for循环
- 03.1.2 while循环
- 03.1.3 range关键字
- 03.1.4 for循环代码示例
- 03.3 Go切片
- 03.3.1 切片基本操作
- 03.3.2 切片的扩容
- 03.3.3 字节切片
- 03.3.4 copy()函数
- 03.3.5 多维切片
- 03.3.6 使用切片的代码示例
- 03.3.7 使用sort.Slice()排序
- 03.4 Go 映射(map)
- 03.4.1 Map值为nil的坑
- 03.4.2 何时该使用Map?
- 03.5 Go 常量
- 03.5.1 常量生成器:iota
- 03.6 Go 指针
- 03.7 时间与日期的处理技巧
- 03.7.1 解析时间
- 03.7.2 解析时间的代码示例
- 03.7.3 解析日期
- 03.7.4 解析日期的代码示例
- 03.7.5 格式化时间与日期
- 03.8 延伸阅读
- 03.9 练习
- 03.10 本章小结
- 9 并发-Goroutines,Channel和Pipeline
- 09.1 关于进程,线程和Go协程
- 09.1.1 Go调度器
- 09.1.2 并发与并行
- 09.2 Goroutines
- 09.2.1 创建一个Goroutine
- 09.2.2 创建多个Goroutine
- 09.3 优雅地结束goroutines
- 09.3.1 当Add()和Done()的数量不匹配时会发生什么?
- 09.4 Channel(通道)
- 09.4.1 通道的写入
- 09.4.2 从通道接收数据
- 09.4.3 通道作为函数参数传递
- 09.5 管道
- 09.6 延展阅读
- 09.7 练习
- 09.8 本章小结