事情是這樣的,線上一個服務,啓動後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自動發佈