11

Go进程的HeapReleased上升,但是RSS不下降造成内存泄漏? | yoko blog

 4 years ago
source link: https://pengrl.com/p/20033/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Go进程的HeapReleased上升,但是RSS不下降造成内存泄漏?

发表于 2020-03-16 | 分类于 Go
| 字数统计: 1.2k

事情是这样的,线上一个服务,启动后RSS随任务数增加而持续上升,但是过了业务高峰期后,任务数已经下降,RSS却没有下降,而是维持在高位水平。

那内存到底被谁持有了呢?为了定位问题,我把进程的各项Go runtime内存指标,以及进程的RSS等指标持续采集下来,并以时间维度绘制成了折线图:

内存折线图

本着DRY原则,我把采集和绘制部分专门制作成了一个开源库,业务方代码可以十分方便的接入,绘制出如上样式的折线图,并通过网页实时查看。git地址:https://github.com/q191201771/pprofplus

图中的指标,VMS和RSS是任何linux进程都有的。Sys、HeapSys、HeapAlloc、HeapInuse、HeapReleased、HeapIdle是Go runtime记录的内存情况。

简单来说,RSS可以认为是进程实际占用内存的大小,也是一个进程外在表现最重要的内存指标。HeapReleased是Go进程归还给操作系统的内存。在 《如何分析golang程序的内存使用情况》 这篇老文章中,实验了随着垃圾回收,HeapReleased上升,RSS下降的过程。

但是这次的案例,从图中可以看到,HeapReleased上升,RSS却从来没有下降过。。

我们来具体分析。(以下我就不重复解释各指标的含义了,对照着看上面那两篇文章就好)

首先从业务的任务数来说,从启动时间03-13 17:47:17开始,是持续增长的,到22:17:17之后开始下降,再到03-14 16:17:27之后,又开始上升。之后就是循环反复。这是业务上实际内存需求的特点。

  • VMS和RSS的整体波形一致,维持在一定差值,符合预期。
  • Sys和RSS几乎重叠,说明确实是Go代码使用的内存,符合预期。
  • HeapSys和Sys的波形一致,维持在一个比较小的差值,说明大部分内存都是堆内存,符合预期。
  • HeapInuse和HeapAlloc是持续震荡的,波形一致,维持在一定差值,业务高峰期时上升,低峰期下降,符合预期。
  • HeapIdle在首次高峰前震荡上升,之后一直和HeapInuse的波形相反,说明起到了缓存的作用,符合预期。
  • HeapIdle和HeapReleased波形一致,符合预期。

那么回到最初的问题,为什么HeapReleased上升,RSS没有下降呢?

这是因为Go底层用mmap申请的内存,会用madvise释放内存。具体见go/src/runtime/mem_linux.go的代码。

madvise将某段内存标记为不再使用时,有两种方式MADV_DONTNEEDMADV_FREE(通过标志参数传入):

  • MADV_DONTNEED标记过的内存如果再次使用,会触发缺页中断
  • MADV_FREE标记过的内存,内核会等到内存紧张时才会释放。在释放之前,这块内存依然可以复用。这个特性从linux 4.5版本内核开始支持

显然,MADV_FREE是一种用空间换时间的优化。

  • Go 1.12之前,linux平台下Go runtime中的sysUnsed使用madvise(MADV_DONTNEED)
  • Go 1.12之后,在MADV_FREE可用时会优先使用MADV_FREE

具体见 https://github.com/golang/go/issues/23687

Go 1.12之后,提供了一种方式强制回退使用MADV_DONTNEED的方式,在执行程序前添加GODEBUG=madvdontneed=1。具体见 https://github.com/golang/go/issues/28466

ok,知道了RSS不释放的原因,回到我们自己的问题上,做个总结。

事实上,我们案例中,进程对执行环境的资源是独占的,也就是说机器只有这一个核心业务进程,内存主要就是给它用的。

所以我们知道了不是自己写的上层业务错误持有了内存,而是底层做的优化,我们开心的用就好。

另一方面,我们应该通过HeapInuse等指标的震荡情况,以及GC的耗时,来观察上层业务是否申请、释放堆内存太频繁了,是否有必要对上层业务做优化,比如减少堆内存,添加内存池等。

好,这篇先写到这,最近还有两个线上实际业务的内存案例,也用到了上面的pprofplus画图分析,有空再写文章分享。

本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/20033/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK