golang 内存分析/内存泄漏
pprof
pprof 是 Go 语言中分析程序运行性能的工具,它能提供各种性能数据:
类型 |
描述 |
---|---|
allocs |
内存分配情况的采样信息 |
blocks |
阻塞操作情况的采样信息 |
goroutine |
当前所有协程的堆栈信息 |
heap |
堆上内存的使用情况的采样信息 |
profile |
CPU占用情况的采样信息 |
threadcreate |
系统线程创建情况的采样信息 |
trace |
程序运行跟踪信息 |
以内存分析为例:
推荐直接使用命令进入命令行交互模式:
go tool pprof -alloc_space http://localhost:6061/debug/pprof/heap
可以使用参数指明分析的类型:
inuse_space — amount of memory allocated and not released yet inuse_objects— amount of objects allocated and not released yet alloc_space — total amount of memory allocated (regardless of released) alloc_objects — total amount of objects allocated (regardless of released)
进入交互式模式之后,比较常用的有 top、list、traces、web 等命令。
(1) top
(pprof) top
Showing nodes accounting for 15624.87MB, 50.48% of 30953.89MB total
Dropped 229 nodes (cum <= 154.77MB)
Showing top 10 nodes out of 167
flat flat% sum% cum cum%
6272.15MB 20.26% 20.26% 6272.15MB 20.26% github.com/emicklei/go-restful.CurlyRouter.selectRoutes
1457.12MB 4.71% 30.48% 1457.12MB 4.71% bytes.makeSlice
1177.26MB 3.80% 38.47% 1260.76MB 4.07% net/textproto.(*Reader).ReadMIMEHeader
900.41MB 2.91% 41.38% 987.41MB 3.19% google.golang.org/grpc/internal/transport.(*http2Client).createHeaderFields
780.13MB 2.52% 43.90% 3044.06MB 9.83% net/http.(*conn).readRequest
705.24MB 2.28% 46.18% 705.24MB 2.28% github.com/emicklei/go-restful.sortableCurlyRoutes.routes
678.09MB 2.19% 48.37% 1112.62MB 3.59% google.golang.org/grpc/internal/transport.(*http2Client).newStream
653.03MB 2.11% 50.48% 653.03MB 2.11% context.WithValue
top会列出5个统计数据:
- flat: 本函数占用的内存量。
- flat%: 本函数内存占使用中内存总量的百分比。
- sum%: 前面每一行flat百分比的和,比如第2行虽然的100% 是 100% + 0%。
- cum: 是累计量,加入main函数调用了函数f,函数f占用的内存量,也会记进来。
- cum%: 是累计量占总量的百分比。
(2) list
查看某个函数的代码,以及该函数每行代码的指标信息,如果函数名不明确,会进行模糊匹配,比如
(pprof) list github.com/emicklei/go-restful.CurlyRouter.selectRoutes
Total: 30.45GB
ROUTINE ======================== github.com/emicklei/go-restful.CurlyRouter.selectRoutes in /Users/michaelliu/go/pkg/mod/github.com/emicklei/go-restful@v2.12.0+incompatible/curly.go
6.13GB 6.13GB (flat, cum) 20.11% of Total
. . 43: return detectedService, selectedRoute, nil
. . 44:}
. . 45:
. . 46:// selectRoutes return a collection of Route from a WebService that matches the path tokens from the request.
. . 47:func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
6.06GB 6.06GB 48: candidates := make(sortableCurlyRoutes, 0, 8)
. . 49: for _, each := range ws.routes {
. . 50: matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens, each.hasCustomVerb)
. . 51: if matches {
. . 52: candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
. . 53: }
. . 54: }
64.50MB 64.50MB 55: sort.Sort(candidates)
. . 56: return candidates
. . 57:}
. . 58:
. . 59:// matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are.
. . 60:func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string, routeHasCustomVerb bool) (matches bool, paramCount int, staticCount int) {
可以看到在github.com/emicklei/go-restful.CurlyRouter.selectRoutes
中的第48行占用了6.06GB内存。
(3) traces
traces可以打印所有调用栈,以及调用栈的指标信息。
(pprof) traces github.com/emicklei/go-restful.CurlyRouter.selectRoutes
Type: alloc_space
Time: Sep 20, 2020 at 7:39pm (CST)
-----------+-------------------------------------------------------
bytes: 32B
64.50MB github.com/emicklei/go-restful.CurlyRouter.selectRoutes
github.com/emicklei/go-restful.CurlyRouter.SelectRoute
github.com/emicklei/go-restful.(*Container).dispatch.func3
github.com/emicklei/go-restful.(*Container).dispatch
net/http.HandlerFunc.ServeHTTP
net/http.(*ServeMux).ServeHTTP
github.com/emicklei/go-restful.(*Container).ServeHTTP
net/http.serverHandler.ServeHTTP
net/http.(*conn).serve
-----------+-------------------------------------------------------
bytes: 3kB
6.06GB github.com/emicklei/go-restful.CurlyRouter.selectRoutes
github.com/emicklei/go-restful.CurlyRouter.SelectRoute
github.com/emicklei/go-restful.(*Container).dispatch.func3
github.com/emicklei/go-restful.(*Container).dispatch
net/http.HandlerFunc.ServeHTTP
net/http.(*ServeMux).ServeHTTP
github.com/emicklei/go-restful.(*Container).ServeHTTP
net/http.serverHandler.ServeHTTP
net/http.(*conn).serve
-----------+-------------------------------------------------------
每个- - - - - 隔开的是一个调用栈。
内存泄露
内存泄露指的是程序运行过程中已不再使用的内存,没有被释放掉,导致这些内存无法被使用,直到程序结束这些内存才被释放的问题。
内存profiling记录的是堆内存分配的情况,以及调用栈信息,并不是进程完整的内存情况。基于抽样和它跟踪的是已分配的内存,而不是使用中的内存,(比如有些内存已经分配,看似使用,但实际以及不使用的内存,比如内存泄露的那部分),所以不能使用内存profiling衡量程序总体的内存使用情况。
只能通过heap观察内存的变化,增长与减少,内存主要被哪些代码占用了,程序存在内存问题,这只能说明内存有使用不合理的地方,但并不能说明这是内存泄露。
heap在帮助定位内存泄露原因上贡献的力量微乎其微。能通过heap找到占用内存多的位置,但这个位置通常不一定是内存泄露,就算是内存泄露,也只是内存泄露的结果,并不是真正导致内存泄露的根源。
(1)怎么用heap发现内存问题
使用pprof的heap能够获取程序运行时的内存信息,在程序平稳运行的情况下,每个一段时间使用heap获取内存的profile,然后使用base能够对比两个profile文件的差别,就像diff命令一样显示出增加和减少的变化:
➜ pprof go tool pprof -alloc_space -base pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.149.pb.gz pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.150.pb.gz
Type: alloc_space
Time: Sep 20, 2020 at 7:23pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 221.95MB, 97.36% of 227.97MB total
Dropped 51 nodes (cum <= 1.14MB)
Showing top 10 nodes out of 55
flat flat% sum% cum cum%
199.29MB 87.42% 87.42% 199.29MB 87.42% bytes.makeSlice
9.52MB 4.17% 91.59% 9.52MB 4.17% regexp/syntax.(*compiler).inst (inline)
2.64MB 1.16% 92.75% 2.64MB 1.16% compress/flate.NewWriter
2.50MB 1.10% 93.85% 4.50MB 1.97% regexp/syntax.(*Regexp).Simplify
2MB 0.88% 94.73% 2MB 0.88% regexp/syntax.simplify1 (inline)
2MB 0.88% 95.61% 2MB 0.88% time.NewTimer
1.50MB 0.66% 96.26% 1.50MB 0.66% os.lstatNolog
1.50MB 0.66% 96.92% 1.50MB 0.66% regexp/syntax.(*parser).newRegexp (inline)
0.50MB 0.22% 97.14% 1.50MB 0.66% github.com/go-chassis/go-chassis/pkg/scclient.(*RegistryClient).HTTPDo
0.50MB 0.22% 97.36% 16.01MB 7.02% regexp.compile
(pprof) traces bytes.makeSlice
Type: alloc_space
Time: Sep 20, 2020 at 7:23pm (CST)
-----------+-------------------------------------------------------
bytes: 199.29MB
199.29MB bytes.makeSlice
bytes.(*Buffer).grow
bytes.(*Buffer).Grow
io/ioutil.readAll
io/ioutil.ReadFile
github.com/go-chassis/go-chassis/core/lager.CopyFile
github.com/go-chassis/go-chassis/core/lager.doRollover
github.com/go-chassis/go-chassis/core/lager.logRotateFile
github.com/go-chassis/go-chassis/core/lager.LogRotate
github.com/go-chassis/go-chassis/core/lager.(*rotators).Rotate.func1
-----------+-------------------------------------------------------
bytes: 613.91MB
0 bytes.makeSlice
bytes.(*Buffer).grow
bytes.(*Buffer).ReadFrom
io/ioutil.readAll
io/ioutil.ReadFile
github.com/go-chassis/go-chassis/core/lager.CopyFile
github.com/go-chassis/go-chassis/core/lager.doRollover
github.com/go-chassis/go-chassis/core/lager.logRotateFile
github.com/go-chassis/go-chassis/core/lager.LogRotate
github.com/go-chassis/go-chassis/core/lager.(*rotators).Rotate.func1
-----------+-------------------------------------------------------
bytes: 306.95MB
0 bytes.makeSlice
bytes.(*Buffer).grow
bytes.(*Buffer).Grow
io/ioutil.readAll
io/ioutil.ReadFile
github.com/go-chassis/go-chassis/core/lager.CopyFile
github.com/go-chassis/go-chassis/core/lager.doRollover
github.com/go-chassis/go-chassis/core/lager.logRotateFile
github.com/go-chassis/go-chassis/core/lager.LogRotate
github.com/go-chassis/go-chassis/core/lager.(*rotators).Rotate.func1
-----------+-------------------------------------------------------
(2)goroutine泄露怎么导致内存泄露
每个goroutine占用2KB内存,泄露1百万goroutine至少泄露2KB * 1000000 = 2GB内存。此外goroutine执行过程中还存在一些变量,如果这些变量指向堆内存中的内存,GC会认为这些内存仍在使用,不会对其进行回收,这些内存谁都无法使用,造成了内存泄露。
所以goroutine泄露有2种方式造成内存泄露:
- goroutine本身的栈所占用的空间造成内存泄露。
- goroutine中的变量所占用的堆内存导致堆内存泄露,这一部分是能通过heap profile体现出来的。
分析goroutine本身的栈所占用的空间造成内存泄露,可以通过pprof来查找,方法与heap类似,都是取两次采样做比较。
- 盘点世界十大著名黑客攻击事件
- AS3中的单件(Singleton)模式
- puremvc框架之hello world!
- windows平台下编辑的内容传到linux平台出现中文乱码的解决办法
- puremvc框架之Command
- python sorted函数
- Centos 6.9下部署Oracle 11G数据库环境的操作记录
- puremvc框架之proxy
- Oracle数据库冷备份与热备份操作梳理
- Oracle数据库重做日志及归档日志的工作原理说明
- 用vs.net2010做flex/flash/as3开发
- python中input()与raw_input()的区别到底是啥?
- VB下中文URL编码问题的解决
- 让ZeGraph在X方向上填满
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- LeetCode | 94.二叉树的中序遍历
- Druid 的整合
- LeetCode | 104.二叉树的最大深度
- Flutter 目录结构和项目资源
- iOS音视频接入- TRTC互动直播
- 【一天一大 lee】查找常用字符 (难度:简单) - Day20201014
- 金九银十准备换场地?对标腾讯T3的Android高级工程师面试大纲及时雨来了
- 【一天一大 lee】两两交换链表中的节点 (难度:中等) - Day20201013
- 【一天一大 lee】二叉搜索树的最小绝对差 (难度:简单) - Day20201012
- 有奖互动 | 腾讯云开发者社区 3 周年庆,我过生日,送你们礼物 ~
- 【一天一大 lee】分割等和子集 (难度:中等) - Day20201011
- 【一天一大 lee】寻找两个正序数组的中位数 (难度:困难) - Day20201003
- 【一天一大 lee】颜色分类 (难度:中等) - Day20201007
- 【一天一大 lee】树中距离之和 (难度:困难) - Day20201006
- 在Spring项目中以多线程的方式并发执行,异步处理任务。解决统计、累加类业务的例子。