怎麼避免服務內存溢出?

在高併發、高吞吐的場景下,很多簡單的事情,會變得非常複雜,而很多程序並沒有在設計時針對高併發高吞吐量的情況做好內存管理。

自動內存管理機制的實現原理

做內存管理,主要考慮申請內存和內存回收兩部分。

申請內存的步驟:

  1. 計算要創建的對象需要佔用多少內存
  2. 在內存中找一塊連續並且是空閒的內存空間,標記爲已佔用

內存回收需要主要做2件事情。

  1. 找出所有可以回收的對象,將其標記爲空閒
  2. 整理內存碎片

現代GC算法大部分採用“標記-清除”算法或者他的變種算法,這種算法分爲標記和清除兩個階段:

  • 標記階段:從GC Root開始,可以簡單把GC Root理解爲程序入口的那個對象,標記所有可達的對象,因爲程序中所有在用的對象一定會被這個GC Root直接引用或者間接引用。
  • 清除階段:遍歷所有對象,找出所有沒有標記的對象,這些沒有標記的對象都是可以被回收的,清除這些對象,釋放相應的內存。

“標記-清除”算法的一個最大問題,是在標記和清除過程中,必須先把進程暫停,否則計算的結果就是不準確的。這也是爲什麼發生垃圾回收的時候,我們的程序會卡死的原因。

需要注意,垃圾回收完成後,我們還需要進行內存碎片整理,將不連續的空閒內存移到到一起,以便空出足夠的連續內存空間供後需用。

雖然自動內存管理機制有效地解決了內存泄露問題,帶來的代價是執行垃圾回收時會暫停進程,如果暫停的時間過長,程序看起來就像“卡死了”一樣。

服務爲什麼會在高併發時忽然“卡死”?

在高併發、高吞吐量場景下,我們的程序會非常忙,短時間內會創建大量的對象,這些對象會迅速佔滿內存,由於沒有內存可用,垃圾回收被迫開始啓動,並且,這次被迫執行的垃圾回收面臨的是佔滿整個內存的海量對象,它執行的時間也會增加,相應的,這個回收過程會導致進程長時間暫停。

進程長時間暫停,又會導致大量的請求積壓等待結果,垃圾回收剛剛結束,更多的請求立刻湧來,迅速佔滿內存,再次被迫執行垃圾回收,進入一個惡性循環,如果垃圾回收的速度跟不上創建對象的速度,還可能會產生內存溢出的現象。

高併發下的內存管理技巧

對於開發者來說,垃圾回收是不可控的,而且是無法避免的,但是,我們可以通過一些方法來降低垃圾回收的頻率,減少進程暫停的時長。

我們需要考慮在處理大量請求的同時,儘量少的產生一次性對象,特別是佔用內存比較大的對象。我們可以按照這個思路來優化對象的業務代碼。

對於需要頻繁使用、佔用內存較大的一次性對象,我們可以考慮自行回收並重用這些對象,例如我們可以爲這些對象創建一個對象池,收到請求後,在對象池裏面申請一個對象,使用完後再放回到對象池中,這樣就可以反覆地重複使用這些對象,非常有效地避免頻繁觸發垃圾回收。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章