## 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 本章小結