如何分析golang内存?

117

我写了一个使用golang编程的程序,在运行时使用了1.2GB的内存。

调用go tool pprof http://10.10.58.118:8601/debug/pprof/heap只能得到仅有323.4MB堆使用量的转储。

  • 剩下的内存使用情况如何?
  • 是否有更好的工具来解释golang运行时内存?

使用gcvis我得到了这个:

enter image description here

.. 和这个堆分析结果:

enter image description here

这是我的代码:https://github.com/sharewind/push-server/blob/v3/broker


1
发布你的代码。告诉我们你的程序是做什么的。 - elithrar
可能是因为垃圾回收机制?可以参考 http://dave.cheney.net/2014/07/11/visualising-the-go-garbage-collector。 - VonC
看起来剩余的内存没有被垃圾回收并释放给系统。这是在几分钟的不活动后完成的。等待8分钟后再次检查。请查看此链接以获取有关如何调试/分析Go程序的指南:https://software.intel.com/en-us/blogs/2014/05/10/debugging-performance-issues-in-go-programs - siritinga
请参阅 https://golang.org/pkg/runtime/#MemStats 了解 runtime.MemStats 的详细说明。 - Greg Bray
7个回答

89
堆分析显示活跃的内存,即运行时认为go程序正在使用的内存(即尚未被垃圾回收器收集)。当垃圾回收器收集内存时,分析结果会变小,但没有内存返回给系统。在请求更多内存之前,您未来的分配将尝试使用先前收集的对象池中的内存。
从外部来看,这意味着您的程序的内存使用量将增加或保持不变。外部系统呈现为程序的"常驻大小"是分配给程序的RAM字节数,无论它是保存正在使用的go值还是已收集的值。
这两个数字经常相差很大的原因是:
  1. 垃圾回收对程序的外部视图没有影响
  2. 内存碎片化
  3. 垃圾回收仅在使用的内存比上一个GC之后的使用内存增加一倍时运行(默认情况下,请参见: http://golang.org/pkg/runtime/#pkg-overview
如果您想获得Go视图中内存的准确分解,可以使用runtime.ReadMemStats调用:http://golang.org/pkg/runtime/#ReadMemStats
另外,在使用基于Web的分析时,如果可以访问调试数据,则可以通过浏览器访问: http://10.10.58.118:8601/debug/pprof/ 的堆链接,将显示堆分析的调试视图,其中在底部有一个runtime.MemStats 结构的打印输出。
runtime.MemStats文档(http://golang.org/pkg/runtime/#MemStats)解释了所有字段,但本文讨论的有趣字段是:
  • HeapAlloc:基本上是分析器提供给您的(活动堆内存)
  • Alloc:类似于HeapAlloc,但对于所有go管理的内存
  • Sys:从操作系统请求的内存(地址空间)的总量

Sys和操作系统报告之间仍然会存在差异,因为Go请求系统的内容与操作系统提供的内容并不总是相同的。此外,CGO/syscall(例如:malloc/mmap)内存不会被Go跟踪。


我正在使用Go 1.3.3和基于Web的分析,但/debug/pprof/heap没有运行时MemStats结构的打印输出。 - IanB
7
“没有内存被归还给系统”现在不是完全准确的。请参阅godoc runtime/debug #FreeOSMemory()。 - jimx
2
这在过去可能有所不同,但根据当前关于runtime.MemStats的文档,AllocHeapAlloc具有相同的含义。 - julen
当Sys大于Resident memory时,这意味着什么?在我的情况下,Alloc为778MB,Sys为2326MB,Resident memory为498MB。如果Resident memory的值大于Sys值,我可以理解,因为这意味着操作系统没有提供程序请求的所有内容。但相反的情况无法解释。 - Rohanil

41
作为对@Cookie of Nine答案的补充,简单来说:您可以尝试使用--alloc_space选项。 go tool pprof默认使用--inuse_space。它会抽样内存使用情况,因此结果是实际使用情况的子集。
通过--alloc_space,pprof返回自程序启动以来分配的所有内存。

1
“--alloc_space” 正是我正在寻找的。 - Max Malysh

31

UPD (2022)

对于那些懂俄语的人,我做了一个演示并写了几篇关于这个主题的文章:

  1. Golang中的RAM消耗:问题和解决方案(Потребление оперативной памяти в языке Go: проблемы и пути решения)
  2. 在Go中防止内存泄漏,第1部分。业务逻辑错误(Предотвращаем утечки памяти в Go, ч. 1. Ошибки бизнес-логики)
  3. 在Go中防止内存泄漏,第2部分。运行时特性(Предотвращаем утечки памяти в Go, ч. 2. Особенности рантайма)

原始回答(2017)

我一直对我的Go应用程序不断增长的住宅内存感到困惑,最后我不得不学习Go生态系统中存在的分析工具。运行时提供了许多度量标准,其中包括runtime.Memstats结构,但是很难理解哪些度量标准可以帮助找出内存增长的原因,因此需要一些额外的工具。

分析环境

在你的应用程序中使用 https://github.com/tevjef/go-runtime-metrics。例如,你可以将以下内容放入你的 main 函数中:

import(
    metrics "github.com/tevjef/go-runtime-metrics"
)
func main() {
    //...
    metrics.DefaultConfig.CollectionInterval = time.Second
    if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
        // handle error
    }
}

在Docker容器中运行InfluxDB和Grafana:
docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0

建立GrafanaInfluxDB之间的交互:Grafana(Grafana主页 ->左上角 ->数据源 ->添加新数据源):

enter image description here

https://grafana.com 导入仪表盘 #3242 (Grafana主页 -> 左上角 -> 仪表盘 -> 导入):

enter image description here

最后,启动您的应用程序:它将向容器化的Influxdb传输运行时指标。将您的应用程序置于合理的负载下(在我的情况下,它非常小-每秒5个请求数持续几个小时)。 内存消耗分析
  1. SysRSS的同义词)曲线与HeapSys曲线非常相似。结果发现动态内存分配是整体内存增长的主要因素,因此堆栈变量所消耗的少量内存似乎是恒定的,可以忽略;
  2. 常量数量的goroutine保证了goroutine泄漏/堆栈变量泄漏的不存在;
  3. 在进程的生命周期内,分配的对象总数保持不变(考虑到波动没有意义)。
  4. 最令人惊讶的事实是:HeapIdle以与Sys相同的速度增长,而HeapReleased始终为零。显然,在这个测试条件下,运行时根本不会将内存返回给操作系统
HeapIdle minus HeapReleased estimates the amount of memory    
that could be returned to the OS, but is being retained by
the runtime so it can grow the heap without requesting more
memory from the OS.

enter image description hereenter image description here

对于那些试图调查内存消耗问题的人,我建议按照以下步骤排除一些琐碎的错误(例如goroutine泄漏)。

显式释放内存

有趣的是,通过显式调用debug.FreeOSMemory()可以显著减少内存消耗:

// in the top-level package
func init() {
   go func() {
       t := time.Tick(time.Second)
       for {
           <-t
           debug.FreeOSMemory()
       }
   }()
}

comparison

事实上,与默认条件相比,这种方法节省了约35%的内存。


很棒的写作。不幸的是,InfluxDB已经不再支持创建数据库。所引用的库在创建数据库步骤中失败了。 - rams

10
您还可以使用StackImpact,它会自动记录和报告常规和异常触发的内存分配概况到仪表板上,并以历史和可比较的形式提供。请查看此博客文章以获取更多详细信息:在生产 Go 应用程序中检测内存泄漏

输入图像描述

声明:我为 StackImpact 工作


我已经尝试了StackImpact,但内存泄漏问题变得更加严重了。其中一个内存泄漏点是https://pastebin.com/ZAPCeGmp。 - Vlad
看起来你正在使用 --alloc_space,这不适用于内存泄漏检测。它只会显示自程序启动以来分配了多少内存。对于长时间运行的程序,这些数字可能会变得非常高。到目前为止,我们还没有发现 StackImpact 代理中存在任何内存泄漏问题。 - logix

1

尝试使用Tracy的GO插件。Tracy是“实时、纳秒级分辨率、远程遥测”工具(...)。GoTracy(插件名称)是与Tracy连接并发送必要信息以更好地了解您的应用程序过程的代理。导入插件后,您可以像下面的描述中一样放置遥测代码:

func exampleFunction() {
    gotracy.TracyInit()
    gotracy.TracySetThreadName("exampleFunction")
    for i := 0.0; i < math.Pi; i += 0.1 {

        zoneid := gotracy.TracyZoneBegin("Calculating Sin(x) Zone", 0xF0F0F0)
        gotracy.TracyFrameMarkStart("Calculating sin(x)")
        sin := math.Sin(i)
        gotracy.TracyFrameMarkEnd("Calculating sin(x)")
        gotracy.TracyMessageLC("Sin(x) = "+strconv.FormatFloat(sin, 'E', -1, 64), 0xFF0F0F)
        gotracy.TracyPlotDouble("sin(x)", sin)
        gotracy.TracyZoneEnd(zoneid)

        gotracy.TracyFrameMark()
    }
}

结果类似于: enter image description here

插件位于: https://github.com/grzesl/gotracy

Tracy 位于: https://github.com/wolfpld/tracy


1

1
这篇文章将对你的问题非常有帮助。 https://medium.com/safetycultureengineering/analyzing-and-improving-memory-usage-in-go-46be8c3be0a8 我运行了pprof分析。pprof是Go语言内置的工具,可以从运行中的应用程序收集分析和可视化数据。它是一个非常有用的工具,可以收集运行中的Go应用程序数据,并且是性能分析的一个很好的起点。我建议在生产环境中运行pprof,这样您就可以获得客户实际操作的样本。
当您运行pprof时,会得到一些文件,这些文件会根据您的配置关注goroutines、CPU、内存使用情况和其他一些内容。我们将专注于堆文件以深入了解内存和GC统计信息。我喜欢在浏览器中查看pprof,因为我发现它更容易找到可操作的数据点。您可以使用以下命令来实现这一点。
go tool pprof -http=:8080 profile_name-heap.pb.gz pprof还有一个CLI工具,但我更喜欢浏览器选项,因为我发现它更容易导航。我的个人建议是使用火焰图。我发现这是最容易理解的可视化工具,所以我大部分时间都使用这个视图。火焰图是函数堆栈跟踪的可视版本。顶部的函数是被调用的函数,而下面的所有内容都是在执行该函数期间调用的。您可以单击各个函数调用来放大它们,从而更改视图。这让您深入了解特定函数的执行情况,非常有帮助。请注意,火焰图显示消耗最多资源的函数,因此某些函数不会出现在其中。这使得更容易找出最大的瓶颈所在。
这对你有帮助吗?

1
谢谢您提供建议,但这并没有增加太多信息。原帖作者显然熟悉pprof,他们的问题与为什么总内存使用量与pprof堆报告的内存使用量存在差异有关。 - craigb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接