最近在项目中出现golang内存溢出的问题,master刚开始运行时只有10多M,运行几天后,居然达到了10多个G。并且到凌晨流量变少内存也没有明显下降,内存状态呈现一种很不健康的曲线。golang

clipboard.png

clipboard.png

像这种状况确定是golang内存溢出了,为此我持续排查了两天,终于找到问题所在,特此记录下。web

准备工做

//引入pprof
import _"net/http/pprof"
//在main中加入
go func() {
    log.Println(http.ListenAndServe("localhost:9999", nil))
}()

开始进行分析

go是一门本身gc的语言,大概两分钟会gc一次。若是有内存泄漏,无非就是两种状况。数据结构

  • 有goroutine泄漏,goroutine“飞”了,zombie goroutine没有结束,这个时候在这个goroutine上分配的内存对象将一直被这个僵尸goroutine引用着,进而致使gc没法回收这类对象,内存泄漏。
  • 有一些全局(或者生命周期和程序自己运行周期同样长的)的数据结构意外的挂住了本该释放的对象,虽然goroutine已经退出了,可是这些对象并无从这类数据结构中删除,致使对象一直被引用,没法被回收。

排除掉goroutine泄漏

首先,我利用压测工具对server进行100个websocket链接,模拟用户浏览行为,而后关闭链接。打开浏览器查看goroutine数量,发现新起的goroutine所有已经销毁,没有观察到有泄漏的goroutine,所以排除此状况。socket

肯定是全局变量无回收

go tool pprof -inuse_space http://127.0.0.1:9999/debug/pprof/heappnglen(map)

后记

为何会花了两天时间,看起来上述流程并不复杂。测试

  • 实际上你要彻底排除掉goroutine泄漏须要花较长的时间去对比的,查看哪些goroutine是新起来没有关闭。
  • 在使用-inuse_space或者-alloc_space分析,也是很纠结,这些看起来也并不彻底与表现对应上。实际上用-inuse_space是较为直观的,能够展示出程序真正在使用的(RSS)。Go 管理内存的方式可能与你之前使用的方式不太同样。它会在一开始就保留一大块 VIRT,而 RSS 与实际内存用量接近。RSS 和 VIRT 之间有什么区别呢?VIRT 或者虚拟地址空间大小是程序映射并能够访问的内存数量。RSS 或者常驻大小是实际使用的内存数量。所以用-inuse_space导出在png图上的统计中,与top上的res值是大体相同。clipboard.png
  • 还有就是每次作压测或者等待golang 彻底gc都要耗费很多时间,这样也会排查增长难度。