事情是这样的,线上一个服务,启动后RSS随任务数增长而持续上升,可是过了业务高峰期后,任务数已经降低,RSS却没有降低,而是维持在高位水平。html
那内存到底被谁持有了呢?为了定位问题,我把进程的各项Go runtime内存指标,以及进程的RSS等指标持续采集下来,并以时间维度绘制成了折线图:linux
本着DRY原则,我把采集和绘制部分专门制做成了一个开源库,业务方代码能够十分方便的接入,绘制出如上样式的折线图,并经过网页实时查看。git地址:github.com/q191201771/…git
图中的指标,VMS和RSS是任何linux进程都有的。Sys、HeapSys、HeapAlloc、HeapInuse、HeapReleased、HeapIdle是Go runtime记录的内存状况。github
简单来讲,RSS能够认为是进程实际占用内存的大小,也是一个进程外在表现最重要的内存指标。HeapReleased是Go进程归还给操做系统的内存。在 《如何分析golang程序的内存使用状况》 这篇老文章中,实验了随着垃圾回收,HeapReleased上升,RSS降低的过程。golang
可是此次的案例,从图中能够看到,HeapReleased上升,RSS却历来没有降低过。。缓存
咱们来具体分析。(如下我就不重复解释各指标的含义了,对照着看上面那两篇文章就好)优化
首先从业务的任务数来讲,从启动时间03-13 17:47:17
开始,是持续增加的,到22:17:17
以后开始降低,再到03-14 16:17:27
以后,又开始上升。以后就是循环反复。这是业务上实际内存需求的特色。操作系统
那么回到最初的问题,为何HeapReleased上升,RSS没有降低呢?3d
这是由于Go底层用mmap申请的内存,会用madvise释放内存。具体见go/src/runtime/mem_linux.go
的代码。code
madvise将某段内存标记为再也不使用时,有两种方式MADV_DONTNEED
和MADV_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
Go 1.12
以后,提供了一种方式强制回退使用MADV_DONTNEED
的方式,在执行程序前添加GODEBUG=madvdontneed=1
。具体见 github.com/golang/go/i…
ok,知道了RSS不释放的缘由,回到咱们本身的问题上,作个总结。
事实上,咱们案例中,进程对执行环境的资源是独占的,也就是说机器只有这一个核心业务进程,内存主要就是给它用的。
因此咱们知道了不是本身写的上层业务错误持有了内存,而是底层作的优化,咱们开心的用就好。
另外一方面,咱们应该经过HeapInuse等指标的震荡状况,以及GC的耗时,来观察上层业务是否申请、释放堆内存太频繁了,是否有必要对上层业务作优化,好比减小堆内存,添加内存池等。
好,这篇先写到这,最近还有两个线上实际业务的内存案例,也用到了上面的pprofplus画图分析,有空再写文章分享。
本文完,做者yoko,尊重劳动人民成果,转载请注明原文出处: pengrl.com/p/20033/
本篇文章由一文多发平台ArtiPub自动发布