记一次go服务内存异常增涨

这几天发现服务的内存一直往上涨,这是监控看到的图标,可以一眼看出

 

最后一段线变平了是因为业务方的调用停掉了。

 

 

遇到这种情况,首先想到的是查看内存分布图,于是调用pprof,拿到内存分布图

 

 

 

我们的机器是16G的,从监控图表看到内存占用达到了百分之50多,将近10个G,但是pprof那边显示内存占用只有2.58G,而且可以清楚的看到,其中两个G是copy函数生成的,0.5个G是加载字典文件的内存,从图上面看,内存分布完全符合我们的预期。但是机器上显示占用了10个G,那多余的7个多G去哪了?

为了搞明白这个,于是打印了GC的信息

{"level":"debug","msg":"GC-0:sys=10128MB HeapAlloc=5028MB HeapSys=9661MB","time":"2020-07-24 06:45:30.549"} {"level":"debug","msg":"GC-1:sys=10128MB HeapAlloc=2659MB HeapSys=9661MB","time":"2020-07-24 06:45:31.135"} {"level":"debug","msg":"GC-0:sys=10128MB HeapAlloc=4702MB HeapSys=9661MB","time":"2020-07-24 07:15:07.134"} {"level":"debug","msg":"GC-1:sys=10128MB HeapAlloc=2639MB HeapSys=9661MB","time":"2020-07-24 07:15:07.733"} {"level":"debug","msg":"GC-0:sys=10128MB HeapAlloc=5048MB HeapSys=9661MB","time":"2020-07-24 07:30:45.724"} {"level":"debug","msg":"GC-1:sys=10128MB HeapAlloc=2650MB HeapSys=9661MB","time":"2020-07-24 07:30:46.327"} {"level":"debug","msg":"GC-0:sys=10128MB HeapAlloc=4985MB HeapSys=9661MB","time":"2020-07-24 07:45:28.112"} {"level":"debug","msg":"GC-1:sys=10128MB HeapAlloc=2663MB HeapSys=9661MB","time":"2020-07-24 07:45:28.672"} {"level":"debug","msg":"GC-0:sys=10128MB HeapAlloc=4725MB HeapSys=9661MB","time":"2020-07-24 08:15:07.868"} {"level":"debug","msg":"GC-1:sys=10128MB HeapAlloc=2650MB HeapSys=9661MB","time":"2020-07-24 08:15:08.444"}

 

这是正式服的数据,GC-0是触发垃圾回收前的数据,GC-1是完成垃圾回收后的数据。可以看到 HeapSys用了9个多G,sys10个G,HeapAlloc则是2.5个G和5个G

5个G是垃圾回收之前打印的信息,而2.5个G是垃圾回收之后打印的信息,可以看到,垃圾回收之后HeapAlloc内存明显减小了。

但是为什么另外两个指标没减下去呢?

我在测试服加了些指标打印,如下:

{"level":"info","msg":"GC-0:sys=6109MB HeapAlloc=4856MB HeapSys=5822MB HeapIdle=495MB HeapInuse=5327MB HeapReleased=0MB HeapObjects=24977750","time":"2020-07-22 16:36:52.159"}

{"level":"info","msg":"GC-1:sys=6112MB HeapAlloc=2673MB HeapSys=5822MB HeapIdle=2680MB HeapInuse=3142MB HeapReleased=0MB HeapObjects=16438129","time":"2020-07-22 16:36:52.883"}

首先搞清楚每个指标的含义:

sys是程序所占用的堆空间+栈空间(包含虚拟空间)

HeapSys是程序占用 的堆空间(包含虚拟空间)

HeapAlloc是程序对象实际使用的堆空间,即span里面的object被分配出去的空间

HeapIdle是空闲的堆空间(包含虚拟空间)

HeapReleased是被释放的堆空间

HeapInuse是在使用的堆空间(只要span里面的一个object被使用了,整个span都被计入其中。所以这个比HeapAlloc大)

 

结合前面的pprof,可以看到HeapAlloc基本与pprof图标看到的内存消耗吻合,而且一直维持在2.5g左右,说明我们程序内存还是挺正常的,并没有产生内存泄漏。

结合go的内存分配策略,go在申请内存的时候,会预先申请一块大内存以备用。比如我程序需要用1个G的内存,他有可能会申请1.5个G,而且当垃圾回收后,程序内存释放,也只是归还到mspan,mcentral和mheap。并不会归还给操作系统。当正真归还给操作系统,大概在5分钟之后,前提是内存过多。

 

显然10个G的内存太多了,最终也没归还给系统。具体问题就变成了HeapSys的空闲内存为啥没返回给操作系统。而HeapSys = HeapInuse + HeapIdle,所以就是HeapIdle(空闲的堆空间)为啥没把内存返还给操作系统。

 

这边于是做了一个实验,就写了个程序去抢占内存。最终发现当内存不够用的时候,go会释放多余的空间。HeapReleased=2657MB,显示释放了2657MB空间(测试环境)。

但是在打印的堆信息上面,HeapIdle还是没有减小。这是因为那边显示的是虚拟空间,实际占用的空间已经还回去了。

进一步发现,HeapReleased是HeapIdle的子集,HeapIdle-HeapReleased 就是程序实际可用的空闲空间(未分配的span)。

 

最终通过百度发现,go在1.12版本的时候修改了他的内存回收策略,变成了惰性回收。

当系统内存不够的时候,go才会把这部分内存归还系统。这样做的好处是go向系统申请空间的时候,可以复用之前未被回收的堆内存,而不会触发缺页异常,从而导致内存重新分配。

 

我们可以通过参数GODEBUG=madvdontneed=1 回退会1.11版本的回收策略。

即GODEBUG=madvdontneed=1 ./server

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值