目录
一.理解GO GC机制
1.1GC的耗时
1.2堆大小对GC的影响
1.3GC算法
二 如何查看GC信息
2.1使用GODEBUG="gotrace=1"
2.2 go tool trace
2.3 debug.ReadGCStats
编辑2.4 runtime.ReadMemStats
三 GC优化技巧
2.1并发GC
一.理解GO GC机制
GO语言采用的是三色标记法垃圾回收算法,这种算法采用的是并发标记,扫描完成后再进行清除工作,这意味着性能上可能会付出一些代价。
1.1GC的耗时
在垃圾收集周期内,所有的goroutine必须停止运行以便垃圾回收器可以尽可能多地清除内存。GC会带来一些显著的延迟,大部分gc耗时都集中于标记阶段。越大的堆空间,GC的时间也越长。
1.2堆大小对GC的影响
每个程序都有一个堆大小,这个大小是通过runtime.GOMAXPROCS和runtime.GOGC这两个环境变量来控制的。runtime.GOGC默认是100,意味着当达到100%的可用内存时,GC将开始在程序运行期间自动执行垃圾回收。而runtime.GOMAXPROCS控制程序并发时最大的goroutine数量。
//todo举个例子
1.3GC算法
Go语言采用三色标记算法的主要优点是能够在很短的时间内清理整个堆,避免了没有必要的清理操作。三色标记算法具有最小停顿时间和实时清理堆的能力,但是,它需要非常高的内存扫描性能。这是由于算法需要对对象执行读取和写入操作。所以我们需要在内存使用量和最小停顿时间之间进行平衡。
//todo 三色标记法
二 如何查看GC信息
2.1使用GODEBUG="gotrace=1"
package main
func main() {
//循环分配很大内存,触发GC
for n := 1; n < 100; n++ {
a := make([]byte, 1<<20)
a = a
}
}
命令行使用 GODEBUG="gctrace=1"
字段解析:
gc 2 | 这是第2次gc。 |
@0.041s | 这次gc的markTermination阶段完成后,距离runtime启动到现在的时间。 |
1% | 到目前为止,gc的标记工作(包括两次mark阶段的STW和并发标记)所用的CPU时间占总CPU的百分比 |
0.053+0.95+0.004 ms clock, | 按顺序分成三部分 0.053表示mark阶段的STW时间(单P的); 0.95表示并发标记用的时间(所有P的); 0.004表示markTermination阶段的STW时间(单P的)。 |
0.21+0.093/0.35/0.60+0.018 ms cpu | 按顺序分成三部分 0.21表示整个进程在mark阶段STW停顿时间(0.013 * 8); 0.093/0.35/0.60有三块信息,0.093是mutator assists占用的时间, 0.35是dedicated mark workers+fractional mark worker占用的时间, 0.60是idle mark workers占用的时间。 0.018 ms表示整个进程在markTermination阶段STW停顿时间。 |
3->3->0 MB, | 按顺序分成三部分, 3表示开始markTermination阶段前的heap_live大小; |
4 MB goal, 0 MB stacks, 0 MB globals, | 表示下一次触发GC的内存占用阀值是4MB, |
4 P | 本次gc共有多少个P。 |
2.2 go tool trace
package main
import (
"os"
"runtime/trace"
)
func GoTrace() {
f, _ := os.Create("trace.out")
defer f.Close()
trace.Start(f)
defer trace.Stop()
//循环分配很大内存,触发GC
for n := 1; n < 100; n++ {
a := make([]byte, 1<<20)
a = a
}
}
代码执行完成后可以看到会生成trace.out文案,使用 go tool trace命令查看trace文件
go tool trace -http 127.0.0.1:8080 trace.out
2.3 debug.ReadGCStats
package main
import "time"
func main() {
go RunTimeDebugReadGc()
//循环分配很大内存,触发GC
for n := 1; n < 100; n++ {
a := make([]byte, 1<<20)
a = a
}
//方便观察 sleep 10s
time.Sleep(10 * time.Second)
}
func RunTimeDebugReadGc() {
//每秒触发一次
tracker := time.NewTicker(1 * time.Second)
gcStat := debug.GCStats{}
for {
select {
case <-tracker.C:
debug.ReadGCStats(&gcStat)
fmt.Println(gcStat)
}
}
}
2.4 runtime.ReadMemStats
func RunTimeDebugReadMem() {
//每秒触发一次gc
tracker := time.NewTicker(1 * time.Second)
gcStat := runtime.MemStats{}
for {
select {
case <-tracker.C:
runtime.ReadMemStats(&gcStat)
fmt.Println(gcStat)
}
}
}
三 GC优化技巧
3.1并发GC
并发GC是指GC开始之前,应用程序仍然可以继续执行。 Go语言的GC只有在程序不能继续执行时才运行。同样,因为垃圾回收时程序不工作,因此并发垃圾回收可以大大减少程序停止运行的时间。
3.2非对齐内存分配
内存对接分配可以参考
非对齐内存分配是指在堆上申请非对齐内存块。这是一种减少内存碎片的方法。内存碎片是指多个内存块之间的间隙。通常,当使用堆管理器来管理内存时,垃圾回收时需要在堆中移动和调整不同的内存块,这会导致大量的内存拷贝。如果能够减少碎片,GC的效率就会更高。
3.3手动触发GC
手动触发GC是指在程序运行期间显式调用runtime.GC函数。虽然这不是最激进的解决方案,但它可以有效地减少GC的延迟。
3.4调整GOGC
在程序高峰期跑GC可能会对性能造成严重影响。可以适当调整runtime.GOGC的值来减少GC的频率,根据应用程序的工作负载和内存使用模式,应适当调整该变量。
3.4避免使用GC
通过避免在程序中使用不必要的动态内存分配和释放,可以减少GC的频率和延迟。此外,还可以使用对象池来重新使用不再使用的对象,从而不会立即释放内存。