Go程式記憶體流失的分析以及避免

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Go程式記憶體流失的分析以及避免

2016-10-18

給系統打壓力,記憶體佔用上去了,停止打壓後,仍然降不下來,就可能是有泄漏。

對於無狀態的服務,串連上有請求過來,記憶體上去了。停了請求,但是記憶體仍然居高不下,等到串連斷開記憶體才降,則session可能存在不合理的引用,造成GC無法回收。

看記憶體佔用的時候不要光盯著top上面的數字,因為Go向系統申請的記憶體不使用後,也不會立刻歸還給系統。也就意味著停止打壓後記憶體佔用不立刻下降屬於正常行為,僅看這一項需要多花點時間觀察一會兒。最好是幾個資料一起關註:一個是程式佔用的系統記憶體,另一個是Go的堆記憶體,最後一個是實際使用到的記憶體。從系統申請的記憶體會在Go的記憶體池管理,整塊的記憶體頁,長時間不被訪問並滿足一定條件後,才歸還給作業系統。又因為有GC,堆記憶體也不能代表記憶體佔用,清理過之後剩下的,才是實際使用的記憶體。調用runtime.ReadMemStats可以看到Go的記憶體使用量資訊; 或者啟用net/pprof後訪問 http://127.0.0.1:6060/debug/pprof/heap ,也可以看,其中HeapInuse是實際的記憶體使用量量,具有參考意義; 還可以帶上參數debug/pprof/heap?debug=2之類得到更細的資訊,

發現問題後,首先不要懷疑Go語言的GC有問題,一定要相信是自己的代碼寫的有問題。記憶體釋放不掉,不是泄漏的,而是代碼肯定有什麼地方還引用著那塊記憶體,導致GC無法釋放。怎麼查問題呢?Go語言有pprof這個神器。

經過在上一步看HeapInuse,確認過記憶體沒釋放之後,可以用 go tool pprof -inuse_space http://127.0.0.1:6060/debug/pprof/heap 查看是什麼地方佔用了記憶體,這裡可以大致看到是什麼函數占記憶體,根據代碼可以推測出一些資訊。一般來講,很有可能的就是goroutine leak,然后里面引用到的記憶體都釋放不掉。舉一個例子:

ch := make(chan T)go produce(ch) {  // 生產者往ch裡寫資料  ch <- T{}}go consume(ch) {  // 消費者從ch裡讀出資料  <-ch  err := doSomeThing()}

消費者發生err並退出了,不再讀ch,導致生產者阻塞在ch <- T{}上面,然後生產者的goroutine就泄漏了,裡面引用的記憶體永遠無法釋放。這是一個十分常見的情境,比如說開多個worker,worker處理好資料後寫channel,主線程讀channel,但是主線程處理過程中出錯退出,如果處理不當worker就可能泄漏的。

開啟net/pprof之後,通過 http://127.0.0.1:6060/debug/pprof/goroutine?debug=1 可以看到當前的所有goroutine棧,可以找到有哪些goroutine,當前執行到什麼位置,可以找到在哪裡goroutine泄漏了。

上面說了如何分析和定位Go程式的記憶體流失問題,接下來講一下如果避免寫會泄漏的代碼。

第一條原則是,絕對不能由消費者關channel,因為向關閉的channel寫資料會panic。正確的姿勢是生產者寫完所有資料後,關閉channel,消費者負責消費完channel裡面的全部資料:

func produce(ch chan<- T) {    defer close(ch) // 生產者寫完資料關閉channel    ch <- T{}}func consume(ch <-chan T) {    for _ = range ch { // 消費者用for-range讀完裡面所有資料    }}ch := make(chan T)go produce(ch)consume(ch)

為什麼consume要讀完channel裡面所有資料?因為go produce()可能有多個,這樣寫的代碼,在讀完ch可以確定所有produce的goroutine都退出了,不會泄漏。

第二條原則是,利用關閉channel來廣播取消動作。向關閉的channel讀資料永遠不會阻塞,這是進階的技巧。假設消費者拿到資料處理後有error發生,整個動作失敗,那麼需要有某種機制通知生產者停止並退出。

func produce(ch chan<- T, cancel chan struct{}) {    select {      case ch <- T{}:      case <- cancel: // 用select同時監聽cancel動作    }}func consume(ch <-chan T, cancel chan struct{}) {    v := <-ch    err := doSomeThing(v)    if err != nil {        close(cancel) // 能夠通知所有produce退出        return    }}for i:=0; i<10; i++ {    go produce()}consume()

WaitGroup之類的可以配合著用,看自己喜歡的風格。基本上能處理好error情境下的資源釋放,問題就不大。哦,第零條原則是,對於並發的代碼心存敬畏之心,哪怕用Go,哪怕有channel這麼好用的東西!

context裡面的cancel比較值得參考和學習 ,其實沒什麼技巧,就是多看代碼多寫代碼,標準庫的代碼是極好的學習材料。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.