首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 内存分析/内存泄漏

golang 内存分析/内存泄漏

作者头像
ppxai
发布2020-09-23 17:48:59
8.7K0
发布2020-09-23 17:48:59
举报
文章被收录于专栏:皮皮星球皮皮星球

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类似,都是取两次采样做比较。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020年09月20日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • pprof
    • (1) top
      • (2) list
        • (3) traces
        • 内存泄露
          • (1)怎么用heap发现内存问题
            • (2)goroutine泄露怎么导致内存泄露
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档