Go:内存问题

huangapple go评论82阅读模式
英文:

Go: memory issues

问题

我需要你的智慧。

我有一个用Go语言编写的巨大守护进程。前段时间,有一个用户报告说代码中可能存在内存泄漏的问题。

我开始调查这个问题。当我通过主要的代码检查没有找到关于内存泄漏性质的线索时,我尝试关注我的进程是如何工作的。

我的想法很简单:如果我没有成功地删除对某些对象的引用,我的堆应该会不断增长。我编写了以下过程来监视堆:

func PrintHeap() {
    ticker := time.NewTicker(time.Second * 5)
    for {
        <-ticker.C
        st := &runtime.MemStats{}
        runtime.ReadMemStats(st)
        // 从Golang文档中得知:HeapObjects的增加表示对象被分配,
        // 在堆被扫描并且不可达的对象被释放时减少。
        fmt.Println("Heap allocs:", st.Mallocs, "Heap frees:",
            st.Frees, "Heap objects:", st.HeapObjects)
    }
}

这个过程每5秒打印一些关于堆的信息,包括当前分配的对象数量。

现在来说一下这个守护进程的功能。它处理来自某个UDP输入的行。每行都包含某个HTTP请求的一些信息,并被解析成一个典型的Go结构体。这个结构体有一些数字和字符串字段,包括一个用于请求路径的字段。然后对这个结构体进行了很多操作,但这些操作在这里并不重要。

现在,我将输入速率设置为每秒1500行,每行都相当短(你可以理解为:标准请求路径为/)。

运行应用程序后,我可以看到堆大小在某个时间点稳定下来:

Heap allocs: 180301314 Heap frees: 175991675 Heap objects: 4309639
Heap allocs: 180417372 Heap frees: 176071946 Heap objects: 4345426
Heap allocs: 180526254 Heap frees: 176216276 Heap objects: 4309978
Heap allocs: 182406470 Heap frees: 177496675 Heap objects: 4909795
Heap allocs: 183190214 Heap frees: 178248365 Heap objects: 4941849
Heap allocs: 183302680 Heap frees: 178958823 Heap objects: 4343857
Heap allocs: 183412388 Heap frees: 179101276 Heap objects: 4311112
Heap allocs: 183528654 Heap frees: 179181897 Heap objects: 4346757
Heap allocs: 183638282 Heap frees: 179327221 Heap objects: 4311061
Heap allocs: 185609758 Heap frees: 181330408 Heap objects: 4279350

当达到这种状态时,内存消耗停止增长。

现在,我改变了输入的方式,使得每行的长度超过2k个字符(带有巨大的/AAAAA...请求路径),然后奇怪的事情开始发生。

堆大小急剧增长,但在一段时间后仍然变得稳定:

Heap allocs: 18353000513 Heap frees: 18335783660 Heap objects: 17216853
Heap allocs: 18353108590 Heap frees: 18335797883 Heap objects: 17310707
Heap allocs: 18355134995 Heap frees: 18336081878 Heap objects: 19053117
Heap allocs: 18356826170 Heap frees: 18336182205 Heap objects: 20643965
Heap allocs: 18366029630 Heap frees: 18336925394 Heap objects: 29104236
Heap allocs: 18366122614 Heap frees: 18336937295 Heap objects: 29185319
Heap allocs: 18367840866 Heap frees: 18337205638 Heap objects: 30635228
Heap allocs: 18368909002 Heap frees: 18337309215 Heap objects: 31599787
Heap allocs: 18369628204 Heap frees: 18337362196 Heap objects: 32266008
Heap allocs: 18373482440 Heap frees: 18358282964 Heap objects: 15199476
Heap allocs: 18374488754 Heap frees: 18358330954 Heap objects: 16157800

但内存消耗呈线性增长,并且从未停止。我的问题是:对于发生了什么,有什么想法吗?

我考虑过由于大量巨大对象导致的内存碎片问题,但实际上我真的不知道该怎么想。

英文:

I need your wisdom.

I have a huge daemon written in Go. Some time ago a user reported that there might be a memory leak somewhere in the code.

I started investigating the issue. When primary code inspection didn't lead me to any clues about the nature of this leakage, I tried to focus on how my process works.

My idea was simple: if I failed to remove references to certain objects, my heap should be constantly growing. I wrote the following procedure to monitor heap:

func PrintHeap() {
	ticker := time.NewTicker(time.Second * 5)
	for {
		&lt;-ticker.C
		st := &amp;runtime.MemStats{}
		runtime.ReadMemStats(st)
		// From Golang docs: HeapObjects increases as objects are allocated
        // and decreases as the heap is swept and unreachable objects are
		// freed.
		fmt.Println(&quot;Heap allocs:&quot;, st.Mallocs, &quot;Heap frees:&quot;,
			st.Frees, &quot;Heap objects:&quot;, st.HeapObjects)
	}
}

This procedure prints some info about heap each 5 seconds, including the number of objects currently allocated.

Now a few words about what the daemon does. It processes lines from some UDP input. Each line bears some info about a certain HTTP request and is parsed into a typical Go struct. This struct has some numeric and string fields, including one for request path. Then lots of things happens to this struct, but those things are irrelevant here.

Now, I set the input rate to 1500 lines per second, each line being rather short (you may read this as: with standard request path, /).

After running the application I could see that heap size stabilizes at some point of time:

Heap allocs: 180301314 Heap frees: 175991675 Heap objects: 4309639
Heap allocs: 180417372 Heap frees: 176071946 Heap objects: 4345426
Heap allocs: 180526254 Heap frees: 176216276 Heap objects: 4309978
Heap allocs: 182406470 Heap frees: 177496675 Heap objects: 4909795
Heap allocs: 183190214 Heap frees: 178248365 Heap objects: 4941849
Heap allocs: 183302680 Heap frees: 178958823 Heap objects: 4343857
Heap allocs: 183412388 Heap frees: 179101276 Heap objects: 4311112
Heap allocs: 183528654 Heap frees: 179181897 Heap objects: 4346757
Heap allocs: 183638282 Heap frees: 179327221 Heap objects: 4311061
Heap allocs: 185609758 Heap frees: 181330408 Heap objects: 4279350

When this state was reached, memory consumption stopped to grow.

Now, I changed my input in such a way that each line became more than 2k chars long (with a huge /AAAAA... request path), and that's where weird things started to happen.

Heap size grew drastically, but still became sort of stable after some time:

Heap allocs: 18353000513 Heap frees: 18335783660 Heap objects: 17216853
Heap allocs: 18353108590 Heap frees: 18335797883 Heap objects: 17310707
Heap allocs: 18355134995 Heap frees: 18336081878 Heap objects: 19053117
Heap allocs: 18356826170 Heap frees: 18336182205 Heap objects: 20643965
Heap allocs: 18366029630 Heap frees: 18336925394 Heap objects: 29104236
Heap allocs: 18366122614 Heap frees: 18336937295 Heap objects: 29185319
Heap allocs: 18367840866 Heap frees: 18337205638 Heap objects: 30635228
Heap allocs: 18368909002 Heap frees: 18337309215 Heap objects: 31599787
Heap allocs: 18369628204 Heap frees: 18337362196 Heap objects: 32266008
Heap allocs: 18373482440 Heap frees: 18358282964 Heap objects: 15199476
Heap allocs: 18374488754 Heap frees: 18358330954 Heap objects: 16157800

But memory consumption grew liearly and never stopped. My question is: any ideas about what's going on?

I thought about memory fragmentation due to lots of huge objects, but actually I don't really know what to think.

答案1

得分: 1

你可以尝试使用Go内存分析工具。

首先,你需要修改你的程序,以便提供一个内存分析。有几种方法可以实现这个目标。

  1. 你可以使用net/http/pprof包,参考https://golang.org/pkg/net/http/pprof/,如果你可以公开该端点的话。
  2. 你可以使用runtime/pprof包,并在特定事件发生时,如接收到一个信号等,让你的程序将内存分析结果转储到一个已知位置。

之后,你可以使用go tool pprof来分析内存分析结果,你可以通过以下方式调用它:go tool pprof <path/to/executable> <file>,如果你选择将内存分析结果转储到文件中;或者使用go tool pprof <path/to/executable> http://<host>:<port>/debug/pprof/heap,如果你使用了net/http/pprof,然后使用top5命令获取分配了大部分内存的前5个函数。你可以使用list命令查看特定函数的哪些行分配了多少内存。

从这些分析结果开始,你应该能够推断出你观察到的内存增加的原因。

你也可以阅读https://blog.golang.org/profiling-go-programs,该文章还介绍了如何分析CPU使用情况。只需搜索关键词memprofile即可跳转到相关部分。

英文:

You could try the go memory profiling tools.

First you need to alter your program so it provides a memory profile. There are several ways to this.

  1. You can use package net/http/pprof see https://golang.org/pkg/net/http/pprof/ if it is ok for you to publish that endpoint.
  2. You can use package runtime/pprof and make your program dump a memory profile to a known location in response to specific events like receiving a signal or something.

After that you can analyse the memory profile using the go tool pprof
which you can either invoke as go tool pprof &lt;path/to/executable&gt; &lt;file&gt; if you chose to dump a memory profile to a file or as go tool pprof &lt;path/to/executable&gt; http://&lt;host&gt;:&lt;port&gt;/debug/pprof/heap if you used net/http/pprof and use top5 to get the top 5 functions, which allocated most of your memory. You can than use the list command for specific functions to see which lines allocated how much memory.

Starting from that, you should be able to reason about the increase in memory
you are observing.

You can also read about this at https://blog.golang.org/profiling-go-programs which also describes how to profile your cpu usage. Just search for the word memprofile to jump to the relevant parts.

huangapple
  • 本文由 发表于 2017年3月23日 22:13:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/42978613.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定