【问题标题】:How to analyze golang memory?如何分析golang的内存?
【发布时间】:2014-09-11 20:15:44
【问题描述】:

我写了一个 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

【问题讨论】:

标签: performance memory go profile


【解决方案1】:

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

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

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

  1. GC收集内存对程序外观没有影响
  2. 内存碎片
  3. 只有在上一次 GC 后使用的内存是使用内存的两倍时才会运行 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,但适用于所有托管内存
  • Sys:从操作系统请求的内存总量(地址空间)

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

【讨论】:

  • 我正在使用 go 1.3.3 和基于 Web 的分析,但是 /debug/pprof/heap 不包含 runtime.MemStats 结构的打印输出
  • “没有内存返回到系统”现在并不完全准确。请参阅 godoc 运行时/调试 #FreeOSMemory()。
  • 这在过去可能有所不同,但根据runtime.MemStatsAllocHeapAlloc 上的当前文档具有相同的含义。
  • Sys 不仅仅是驻留内存是什么意思?在我的例子中,Alloc 是 778MB,Sys 是 2326MB,驻留内存是 498MB。我可以理解驻留内存是否超过 Sys 值,因为这意味着操作系统没有提供所有程序请求的内容。但相反的情况无法解释。
【解决方案2】:

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

go tool pprof 默认使用--inuse_space。它对内存使用情况进行采样,因此结果是真实的子集。
--alloc_space pprof 返回程序启动后所有分配的内存。

【讨论】:

  • --alloc_space 正是我想要的。
【解决方案3】:

我总是对我的 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 容器中运行 InfluxDBGrafana

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 主页 -> 左上角 -> 数据源 -> 添加新数据源):

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

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

内存消耗分析

  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.

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

显式释放内存

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

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

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

【讨论】:

    【解决方案4】:

    您还可以使用StackImpact,它会自动记录并向仪表板报告定期和异常触发的内存分配配置文件,这些配置文件以历史和可比较的形式提供。有关详细信息,请参阅此博客文章Memory Leak Detection in Production Go Applications

    免责声明:我为 StackImpact 工作

    【讨论】:

    • 我尝试过 StackImpact,但内存泄漏大大增加。内存泄漏点之一pastebin.com/ZAPCeGmp
    • 您使用的好像是--alloc_space,不适合内存泄漏检测。它只会显示自程序启动以来分配了多少内存。对于一个长期运行的程序,这个数字可能会很高。到目前为止,我们还没有发现 StackImpact 代理中存在任何内存泄漏。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-15
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    相关资源
    最近更新 更多