Go進程的HeapReleased上升,可是RSS不降低形成內存泄漏?

事情是這樣的,線上一個服務,啓動後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以後,又開始上升。以後就是循環反覆。這是業務上實際內存需求的特色。操作系統

  • VMS和RSS的總體波形一致,維持在必定差值,符合預期。
  • Sys和RSS幾乎重疊,說明確實是Go代碼使用的內存,符合預期。
  • HeapSys和Sys的波形一致,維持在一個比較小的差值,說明大部份內存都是堆內存,符合預期。
  • HeapInuse和HeapAlloc是持續震盪的,波形一致,維持在必定差值,業務高峯期時上升,低峯期降低,符合預期。
  • HeapIdle在首次高峯前震盪上升,以後一直和HeapInuse的波形相反,說明起到了緩存的做用,符合預期。
  • HeapIdle和HeapReleased波形一致,符合預期。

那麼回到最初的問題,爲何HeapReleased上升,RSS沒有降低呢?3d

這是由於Go底層用mmap申請的內存,會用madvise釋放內存。具體見go/src/runtime/mem_linux.go的代碼。code

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

具體見 github.com/golang/go/i…

Go 1.12以後,提供了一種方式強制回退使用MADV_DONTNEED的方式,在執行程序前添加GODEBUG=madvdontneed=1。具體見 github.com/golang/go/i…

ok,知道了RSS不釋放的緣由,回到咱們本身的問題上,作個總結。

事實上,咱們案例中,進程對執行環境的資源是獨佔的,也就是說機器只有這一個核心業務進程,內存主要就是給它用的。

因此咱們知道了不是本身寫的上層業務錯誤持有了內存,而是底層作的優化,咱們開心的用就好。

另外一方面,咱們應該經過HeapInuse等指標的震盪狀況,以及GC的耗時,來觀察上層業務是否申請、釋放堆內存太頻繁了,是否有必要對上層業務作優化,好比減小堆內存,添加內存池等。

好,這篇先寫到這,最近還有兩個線上實際業務的內存案例,也用到了上面的pprofplus畫圖分析,有空再寫文章分享。

本文完,做者yoko,尊重勞動人民成果,轉載請註明原文出處: pengrl.com/p/20033/

本篇文章由一文多發平臺ArtiPub自動發佈

相關文章
相關標籤/搜索