• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

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

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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

 

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

 

 

遇到这种情况,首先想到的是查看内存分布图,于是调用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

 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Go语言函数相关发布时间:2022-07-10
下一篇:
Go语言slice的那些坑发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap