Golang处理内存溢出方式
作者:dkjhl
背景
最近系统在压测过程中发现主程序在并发增大后会出现主程序闪退现象,几经波折,认为有可能是内存溢出引起的
正好对 Golang 里分析 dump 这块还没怎么涉及,借此契机研究一下。
前言
查看社区后,发现在Golang中,发生内存溢出通常会导致程序直接崩溃退出,而无法像其他语言一样生成core dump文件。
因此,Golang社区提供了一些工具可以帮助用户进行内存分析。
以下是两个常用的工具:
1. pprof
pprof是Golang内置的性能分析工具,在分析内存使用方面非常有用。
通过设置一个标志位并在程序退出时打开pprof服务器,程序可以向pprof服务器报告一些性能数据,例如内存使用情况。
可以在代码中加入以下代码,启动pprof服务器并在本地8080端口进行访问:
import "net/http" import _ "net/http/pprof" func main() { go func() { http.ListenAndServe("localhost:8080", nil) }() // ... }
启动pprof服务器之后,可以访问http://localhost:8080/debug/pprof/heap进行堆内存分析,获取内存占用的堆快照。
上图中框出的这 4 个部分应该是平时最常用的,从上往下分别是:
- 阻塞分析: 比如,goroutine 的 wait。
- 内存分析: 比如,内存泄漏、内存消耗异常等情况。
- 互斥锁分析: 比如,观察代码里用到的sync.RWMutex 和 sync.Mutex 的具体情况。
- CPU 分析: 比如,排查哪些代码较多地占用了 CPU 资源。
以下是一段消耗内存的代码,用于走读流程,如下:
func main() { go func() { http.ListenAndServe("0.0.0.0:8899", nil) }() str := "gejigejigejigejigejigejigejigjeijgiewjiasdiahdkuhakudsfhakdshgkjasdkjgbakjsdfioajewoiepqbibgijqbgoipbjwebkjfqjkw egqhwejbgfaijwebgjqhb" for i := 0; i < 999; i++ { str += str } fmt.Scanln() }
在 web 页面点击「heap」入口,我们可以看到实时内存的使用情况。
再通过以下命令进入到命令交互模式看看效果:
go tool pprof http://localhost:8899/debug/pprof/heap
进去之后输入「top」,就能很直观的看到哪个方法占用了内存。
这里的几个列的含义简单罗列下:
flat
:当前函数所占用的容量。flat%
:当前函数所占用的容量,在总分配容量的百分比。sum%
:是从调用的最外层到当前方法累加使用的容量占总容量的百分比cum
:当前函数以及子函数所占用的容量。cum%
:当前函数以及子函数所占用的容量,在总分配容量的百分比。- 最后一列是函数的名字
可以再输入获得更详细的信息:
list main.main
上述指令会罗列出每行代码占用的容量;其他类似,如下是分析CPU的
2. Go Memstats
Go Memstats是一个Golang的标准库工具,用于收集和显示内存信息。
可以在代码中导入“runtime”包,并调用“runtime.ReadMemStats(&mem)”函数收集内存信息。
然后可以打印内存使用情况的详情,例如总分配量和分配的堆块数量等。
以下是示例代码:
import ( "fmt" "runtime" ) func main() { var mem runtime.MemStats runtime.ReadMemStats(&mem) fmt.Printf("TotalAlloc (Heap) = %v MiB\n", mem.TotalAlloc/mb) fmt.Printf("Alloc = %v MiB\n", mem.Alloc/mb) fmt.Printf("Sys = %v MiB\n", mem.Sys/mb) fmt.Printf("NumGC = %v\n", mem.NumGC) }
这将输出程序中已分配的总内存,已使用的内存量,系统内存使用情况和垃圾回收次数等信息。
使用这些信息,可以分析内存占用量的增长和优化内存使用。
3. 程序 crash 的时候自动创建 dump 文件
大多数时候,我们可能没有条件实时分析程序运行情况。
比如问题在生产环境偶发出现,且无法在测试环境重现。
这个时候我们可以配置当程序 crash 的时候自动保存 dump 文件。
先输入命令「ulimit -a」看下当前是否开启了core file。
这里的数字单位是 block,具体需要根据所在的操作系统一个 block 对应的大小来设置。如果是0的话说明未开启。可以通过:
ulimit -c 1024 或者 ulimit -c unlimited 来设置 dump 文件的最大 size。
如果想要永久有效,则:
echo "ulimit -c unlimited" >> ~/.profile
让程序发生 crash 的时候会自动生成 dump 文件
## 临时有效 export GOBACTRACE=crash ## 永久有效 echo "export GOTRACEBACK=crash " >> ~/.profile
这就意味着,让程序发生 crash 的时候会自动生成 dump 文件。
有了 dump 文件,可以用 gdb 或者 delve 工具来分析了(官方更建议我们使用 delve,对 Golang 的支持更好)
总之啊,无论你使用哪种工具,分析Golang内存溢出问题需要耐心和技巧。
建议不仅可以使用这些工具进行数据分析,同时还应该阅读关于垃圾回收机制和内存管理的文档,尝试实现更好的编码习惯,以减少内存溢出的可能性。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。