内存泄漏或内存压力可以以多种形式出现在整个系统中。通常我们将它们视为bug,但有时它们的根本原因可能是因为设计的问题,设计中的全局占用的内存我们是可以接受的,但如果通过监测发现内存占用空间一直保持持续的增加,这肯定是哪个地方出了问题。
一些常见内存的问题是:
- 内存分配太多,数据表示不正确
- 大量使用反射或字符串
- 使用全局变量
- 孤儿,没有结束的 goroutines
其中90%以上的问题可能是系统中存在没有结束的goroutines导致的,所以一般来说需要先确定是否有持续增加的goroutines。
下面我们讲述怎么用go的pprof工具帮助我们定位内存泄漏问题。
首先go pprof工具可以获取到如下系统信息:
- goroutine – 所有当前 goroutines 的堆栈跟踪
- heap – 活动对象的内存分配的样本
- allocs – 过去所有内存分配的样本
- threadcreate – 导致创建新 OS 线程的堆栈跟踪
- block – 导致阻塞同步原语的堆栈跟踪
- mutex – 争用互斥锁持有者的堆栈跟踪
我们主要关注heap和goroutine两类信息。
heap
heap(堆),大家对堆的概念一般都熟悉,简而言之,这是 OS(操作系统)存储我们代码中对象占用内存的地方。这块内存随后会被“垃圾回收”,或者在非垃圾回收语言中手动释放。堆不是唯一发生内存分配的地方,一些内存也在栈中分配。栈主要是短周期的内存。在 Go 中,栈通常用于在函数闭包内发生的赋值。Go使用栈的另一个地方是编译器“知道”在运行时需要多少内存(例如固定大小的数组)。
栈内存空间一般会随着函数的执行完成二释放,但堆不一样,一般是Go的垃圾回收器采用某种策略检测到堆占用内存中的内容不会被其他对象引用的时候才会释放。