如何分析golang记忆?


78

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

调用将go tool pprof http://10.10.58.118:8601/debug/pprof/heap导致转储仅使用323.4MB的堆。

  • 其余的内存使用情况又如何呢?
  • 有没有更好的工具来解释golang运行时内存?

使用gcvis我得到这个:

在此处输入图片说明

..和此堆表单配置文件:

在此处输入图片说明

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


1
发布您的代码。告诉我们您的程序做什么。
elithrar 2014年


似乎剩余的内存没有被垃圾收集并释放到系统中。闲置几分钟后即可完成。等待8分钟,然后再次检查。检查此链接为如何调试/个人资料,请计划指南:software.intel.com/en-us/blogs/2014/05/10/...
siritinga

另请参见runtime.MemStats在golang.org/pkg/runtime/#MemStats中进行了
Bray

Answers:


66

堆配置文件显示活动内存,运行时认为该内存已被go程序使用(即:垃圾收集器尚未收集)。当GC确实收集内存时,配置文件会缩小,但不会将任何内存返回给系统。您将来的分配将尝试在请求系统提供更多信息之前使用先前收集的对象池中的内存。

从外部看,这意味着您程序的内存使用量将增加或保持不变。外部系统显示为程序的“驻留大小”的是分配给程序的RAM字节数,无论它是保存使用中的值还是收集的值。

这两个数字经常有很大差异的原因是:

  1. GC收集内存对程序的外部视图没有影响
  2. 内存碎片
  3. 仅当先前使用的内存在使用的内存增加一倍时,GC才会运行(默认情况下,请参见:http : //golang.org/pkg/runtime/#pkg-overview

如果要准确了解Go如何查看内存,可以使用运行时。ReadMemStats调用:http : //golang.org/pkg/runtime/#ReadMemStats

或者,如果您正在使用基于Web的分析,则可以通过以下浏览器访问分析数据:http://10.10.58.118:8601/debug/pprof/单击堆链接,将显示堆配置文件的调试视图,该视图具有运行时的打印输出。底部。

runtime.MemStats文档(http://golang.org/pkg/runtime/#MemStats)提供了所有字段的说明,但有趣的是:

  • HeapAlloc:本质上是探查器为您提供的内容(活动堆内存)
  • Alloc:与HeapAlloc类似,但全部用于托管内存
  • Sys:操作系统请求的内存总量(地址空间)

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


我正在使用1.3.3和基于Web的配置文件,但是其中/debug/pprof/heap不包含运行时的打印输出
。MemStats

7
“没有内存返回到系统”现在并不完全准确。请参阅godoc运行时/调试#FreeOSMemory()。
jimx,2015年

2
这可能是过去不同,但根据对当前文档runtime.MemStatsAlloc并且HeapAlloc具有相同的含义。
julen

Sys超过常驻内存意味着什么?就我而言,Alloc是778MB,Sys是2326MB,常驻内存是498MB。我可以了解驻留内存是否超过Sys值,因为这意味着OS并未提供所有程序要求。但是相反的情况是无法解释的。
罗哈尼尔

31

简而言之,作为@Cookie of Nine答案的补充:您可以尝试该--alloc_space选项。

go tool pprof--inuse_space默认使用。它对内存使用情况进行采样,因此结果是实数的子集。
通过--alloc_spacepprof返回自程序启动以来所有已分配的内存。


1
--alloc_space正是我想要的。
马克斯·马里什

20

我一直对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
    }
}

运行InfluxDBGrafana内部Docker容器:

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

设置Grafana和之间的交互InfluxDB Grafana(Grafana主页->左上角->数据源->添加新数据源):

在此处输入图片说明

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

在此处输入图片说明

最后,启动您的应用程序:它将运行时指标传输到contenerized Influxdb。将您的应用程序置于合理的负载下(在我的情况下,它很小-5 RPS持续了几个小时)。

内存消耗分析

  1. Sys的(RSS曲线的同义词)与曲线非常相似HeapSys。事实证明,动态内存分配是整体内存增长的主要因素,因此堆栈变量所消耗的少量内存似乎是恒定的,可以忽略不计。
  2. 固定数量的goroutine确保不存在goroutine泄漏/堆栈变量泄漏;
  3. 在过程的生命周期中,分配的对象总数保持不变(没有必要考虑波动)。
  4. 最令人惊讶的事实:HeapIdle增长率与相同Sys,而HeapReleased始终为零。显然,运行时不返回内存OS所有,至少在这个试验的条件下:
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.

在此处输入图片说明在此处输入图片说明

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

显式释放内存

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

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

比较

实际上,与默认条件相比,此方法节省了大约35%的内存。


7

您还可以使用StackImpact,它会自动记录常规的和异常触发的内存分配配置文件,并将其报告给仪表板,这些配置文件以历史记录和可比较的形式提供。有关更多详细信息,请参见此博客文章。Production Go应用程序中的内存泄漏检测

在此处输入图片说明

免责声明:我为StackImpact工作


我尝试过StackImpact,并且内存泄漏急剧增加。内存泄漏点之一pastebin.com/ZAPCeGmp
Vlad

看来您正在使用--alloc_space,不适用于内存泄漏检测。它只会显示自程序启动以来分配了多少内存。对于长时间运行的程序,该数字可能会很高。到目前为止,我们还没有发现StackImpact代理中有任何内存泄漏。
logix
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.