Golang内存分配算法主要源自Google的TCMalloc算法,TCMalloc将内存分成三层最外层Thread Cache、中间层Central Cache、最里层Page Heap。Thread Cache和Central Cache里放着不同size的空闲内存块,相同size的空闲内存块会以链表的形式排布。申请内存分为两种,<=256KB的对象都被认为是小对象,>256KB的被认为是大对象,直接通过Page Heap来获取。大对象分配内存都是以Page为单位,即大对象内存以Page对齐。如果你看懂了下面的逻辑图,那么你已经理解了RCMalloc算法。
kMinSystemAlloc
),申请TCMalloc自身元数据所使用的内存时,每次至少申请8MB(kMetadataAllocChunkSize
)。这样既可以减少内存碎片,又均摊了系统调用的开销。Go的内存管理思路和TCMalloc一致,内存池+多级对象管理,在Go里内存管理的对象结构主要是:mheap、mspan、arenas、mcentral、mcache。
垃圾回收(Garbage Collection,GC)就是把程序不用的内存空间视为“垃圾”。这里具体是指程序的堆、栈所占用的内存。GC 要做两件事:标记出需要清理的对象,回收标记的清除对象。标记就是从根节点(栈或者全局变量)扫描,每个根节点扫描到底,扫描所有的根节点。根据三色标记法将对象标记为黑色、灰色、白色;回收标为白色的对象,使其可以被再次利用。
为了避免在GC过程中对象之间的引用关系发生变化,导致GC出错(比如在GC过程中由于未扫描到新的引用对象导致错误清除),会停止所有正在运行的协程,即STW(Stop the world)。STW虽然保证了准确性但是对性能也有影响,那么GC和程序运行是否可以并发进行?
在图三被标记为黑色的对象新引用了一个白色对象,但是这个黑色对象不会再次被扫描,白色对象一人会被回收,这样会造成很严重的后果。为了解决漏标的问题,需要使用写屏障机制。
写屏障是在内存进行写操作之前执行的,一般需要满足以下两个原理:
删除写屏障,在GC过程中如果出现在引用删除,所删除的对象依旧会全部保留下来,满足满足弱三色不变式。虽然不用在此STW但是标记删除粒度比较粗,需要被删除的对象只有在下一轮GC中才会被删除。
go的垃圾回收是基于三色标记法,通过合理的使用内存屏障,大大较少了垃圾回收的STW。GC开始就将栈上所有的对象标记为黑色,不需要二次扫描,不需要STW;GC期间任何栈上新建对象均标记为黑色;被删除的对象标记为灰色;新增对象标记为灰色。结合了删除、插入写屏障各自优势。
主要有两种:
runtime.GC()
名词解释
gctrace主要是观察GC各个阶段耗时及GC后的内存情况。gcvis提供了可视化功能,仅支持GO 1.6版本。下面是一段有内存泄漏的问题代码,执行GODEBUG=gctrace=1 go run demo.go
,会得到详细的GC参数
// go 1.19
package main
import (
"fmt"
"net/http"
)
// 内存未被释放
var urlList []string
func main() {
go func() {
for {
data := []byte("http://127.0.0.1")
sData := string(data)
urlList = append(urlList, sData)
}
}()
http.ListenAndServe("0.0.0.0:6060", nil)
}
命令执行结果:
下面介绍输出参数的具体含义,以图片的最后一行为例
gc 10 @0.264s 1%: 0.39+5.0+0.034 ms clock, 1.5+3.2/3.9/1.4+0.13 ms cpu, 4->5->2 MB, 5 MB goal, 4 P
下面贴出官方的解释:
Currently, it is:
gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P
where the fields are as follows:
gc # the GC number, incremented at each GC
@#s time in seconds since program start
#% percentage of time spent in GC since program start
#+...+# wall-clock/CPU times for the phases of the GC
#->#-># MB heap size at GC start, at GC end, and live heap
# MB goal goal heap size
# P number of processors used
The phases are stop-the-world (STW) sweep termination, concurrent
mark and scan, and STW mark termination. The CPU times
for mark/scan are broken down in to assist time (GC performed in
line with allocation), background GC time, and idle GC time.
If the line ends with "(forced)", this GC was forced by a
runtime.GC() call and all phases are STW.
pprof是可视化和分析性能分析数据的工具。下面一段代码
package main
import (
"fmt"
"net/http"
"time"
_ "net/http/pprof"
)
func main() {
var forkNum int
for forkNum < 100 {
forkWorker(forkNum)
forkNum++
}
http.ListenAndServe("0.0.0.0:6060", nil)
}
func forkWorker(i int) {
go func(i int) {
for {
fmt.Println("worker id ", i, "at ", time.Now().Format("2006-01-02"))
}
}(i)
}
上面的代码会一直创建空跑协程。接下来通过pprof工具来分析。
通过web页面
访问http://127.0.0.1:6060/debug/pprof/ 会看到如下页面
点进子页面能查看到更多的信息。
通过终端交互
执行命令 $ go tool pprof http://localhost:6060/debug/pprof/profile?seconds=60
命令执行后需要等待seconds秒(可调整),此时在pprof的命令交互模式,可以详细查看、导出结果。具体命令执行help
查看。
$ go tool pprof http://localhost:6060/debug/pprof/heap
分析程序常驻内存使用情况
$ go tool pprof http://localhost:6060/debug/pprof/goroutine
分析协程数
命令$ go tool pprof http://localhost:6060/debug/pprof/***
最后的内容可以用web方法页面中的内容替换,会看到不同方向下的内存分配情况。
pprof可视化
安装工具
$ brew install gperftools
$ brew install graphviz
安装graphviz需要很多依赖包,根据报错手动安装对应包。我在安装过程中遇到了gdk-pixbuf安装失败,执行下面命令成功后再次安装graphviz就可以了
$ brew install cairo pango gdk-pixbuf libffi
简单demo
package main
import (
"testing"
)
func TestAdd(t *testing.T) {
s := Add()
if s == "" {
t.Errorf("Test.Add error!")
}
}
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add()
}
}
var urlList []string
func Add() string {
data := []byte("http://127.0.0.1")
sData := string(data)
urlList = append(urlList, sData)
return sData
}
分别执行下面命令:
$ go test -bench=. -cpuprofile=cpu.prof
$ go tool pprof -http=:8080 cpu.prof
执行后会弹出web页面:
红色框起来的是二级页面,点进去可以查看更详细的信息。有了这个可视化工具,我们可以清晰的看出函数的调用关系、以及每一步的耗时情况。可以快速的帮我们找到程序的问题。
pprof火焰图
在上面web页面中,点击VIEW->Flame Graph就可以看到火焰图了。
在上面提到的一步$ go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10
,会产生文件~/pprof/pprof.samples.cpu.003.pb.gz
。
执行命令$ go tool pprof -http=:8081 ~/pprof/pprof.samples.cpu.003.pb.gz
也会跳转到pprof的可视化界面。
怎么看火焰图