- 前言
- Go 与操作系统
- Go 内部机制
- Go 基本数据类型
- 4 组合类型的使用
- 5 数据结构
- 6 Go package 中不为人知的知识
- 7 反射和接口
- 8 Go UNIX 系统编程
- 08.1 关于 UNIX 进程
- 08.2 flag 包
- 8.2 flag 包
- 08.3 io.Reader 和 io.Writer 接口
- 08.4 bufio 包
- 08.5 读取文本文件
- 08.6 从文件中读取所需的数据量
- 08.7 为什么我们使用二进制格式
- 08.8 读取 CSV 文件
- 08.9 写入文件
- 08.10 从磁盘加载和保存数据
- 08.11 再看strings包
- 08.12 关于bytes包
- 08.13 文件权限
- 08.14 处理 Unix 信号
- 08.15 Unix 管道编程
- 08.16 遍历目录树
- 08.17 使用 ePBF
- 08.18 关于 syscall.PtraceRegs
- 08.19 跟踪系统调用
- 08.20 User ID 和 group ID
- 08.21 其他资源
- 08.22 练习
- 08.23 总结
- 9 并发 Goroutines、Channel 和 Pipeline
- 10 Go 并发-进阶讨论
- 11 代码测试、优化及分析
- 12 Go 网络编程基础
- 13 网络编程 - 构建服务器与客户端
02.3 垃圾回收
垃圾回收
垃圾回收是释放掉那些不再被使用的的内存空间的过程。换句话说,垃圾回收器会去检查哪些对象超出范围并且不会再被引用到,然后它会去释放掉那些对象占用的内存空间。这个过程是在go程序运行中以并发的方式去进行的,不是go程序执行之前,也不是go程序执行之后。go垃圾回收器实现的说明文档给出了如下声明(runtime下的mgc.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.
垃圾回收是和go线程同时运行的,它是类型精确的,而且多个垃圾回收线程可以并行运行。它是一种使用了写屏障的并发标记清除的垃圾回收方式。它是非分代和非压缩的。使用按P分配区域隔离的大小来完成分配,以最小化碎片,同时消除常见情况下的锁。
这里面有很多术语,我一会儿会解释。但是首先,我会为你展示一种查看垃圾回收过程的参数的方式。幸运的是,Go标准库提供了方法,允许你去学习垃圾回收器的操作以及了解更多关于垃圾回收器在背后所做的事情。相关的代码保存在了gColl.go中,它有三个部分。
gColl.go的第一部分代码段如下所示:
package main
import (
"fmt"
"os"
"runtime"
"runtime/trace"
"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()
方法的目的是去避免每次要写相同代码。
第二部分代码如下:
func main() {
f, err := os.Create("/tmp/traceFile.out")
if err != nil {
panic(err)
}
defer f.Close()
err = trace.Start(f)
if err != nil {
fmt.Println(err)
return
}
defer trace.Stop()
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进行了更多的内存分配。
for i := 0; i < 10; i++ {
s := make([]byte, 100000000)
if s == nil {
fmt.Println("Operation failed!")
}
time.Sleep(time.Millisecond)
}
printStats(mem)
}
在macOS High Sierra上面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垃圾回收器的工作方式,你不会后悔的。
这里有一个技巧可以让你得到更多关于go 垃圾收集器操作的细节,使用下面这个命令。
GODEBUG=gctrace=1 go run gColl.go
所以,如果你在任何 go run
命令前面加上 GODEBUG=gctrace=1
,go就会去打印关于垃圾回收操作的一些分析数据。生成的数据是如下的形式:
gc 4 @0.025s 0%: 0.002+0.65+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.019ms 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
这三个值为例。第一个数值是垃圾回收器要去运行时候的堆内存大小。第二个值是垃圾回收器操作结束时候的堆内存大小。最后一个值就是生存堆的大小。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论