[英]How to deal with Ruby 2.1.2 memory leaks?
我有一個工作進程,它產生多達50個線程並執行一些異步操作(大多數是http調用)。 當我啟動該過程時,它從大約35MB的已用內存開始,並迅速增長到250MB。 從那時起它進一步增長,問題是內存永遠不會停止增長(即使增長階段隨着時間的推移而減少)。 幾天后,進程只會超出可用內存並崩潰。
我做了很多分析和分析,似乎無法找到問題所在。 即使堆大小非常不變,進程內存也在不斷增長。 我已將GC.stat
輸出收集到您可以在此處訪問的電子表格中:
https://docs.google.com/spreadsheets/d/17TohDNXQ_MXM31CeAmR2ptHFYfvOeF3dB6WCBkBS_Bc/edit?usp=sharing
即使過程內存最終穩定在415MB,但它將在接下來的幾天內持續增長,直到達到512MB的限制並崩潰。
我也嘗試使用對象空間跟蹤對象,但跟蹤對象的內存總和從不超過70-80MB,這與GC報告完全一致。 剩余的300MB +(和不斷增長的)花在哪里..我不知道。
如何處理這類問題? 是否有任何工具可以讓我更清楚地了解內存的使用方式?
更新:寶石和操作系統
我正在使用以下寶石:
gem "require_all", "~> 1.3"
gem "thread", "~> 0.1"
gem "equalizer", "~> 0.0.9"
gem "digest-murmurhash", "~> 0.3", require: "digest/murmurhash"
gem "google-api-client", "~> 0.7", require: "google/api_client"
gem "aws-sdk", "~> 1.44"
該應用程序部署在heroku上,但在Mac OS X 10.9.4上本地運行時,內存泄漏是顯而易見的。
更新:泄漏
我已經升級了stringbuffer並分析了所有像@mtm建議的內容,現在leak
工具沒有發現內存泄漏,隨着時間的推移ruby堆大小沒有增加,然而,進程內存仍然在增長。 最初我認為它在某些時候停止了增長,但幾個小時之后它就超過了極限並且過程崩潰了。
從GC日志看,問題不是ruby對象引用泄漏,因為heap_live_slot
值沒有顯着增加。 這表明問題是:
值得注意的是,問題出現在OSX和Heroku(Ubuntu Linux)上。
Ruby 2.1垃圾收集僅對包含少量數據的對象使用報告的“堆”。 當Object中包含的數據超過某個限制時,數據將被移動並分配給堆外部的區域。 您可以使用ObjectSpace獲取每種數據類型的總體大小:
require 'objspace'
ObjectSpace.count_objects_size({})
將此與GC統計信息一起收集可能會指示在堆外部分配內存的位置。 如果您找到特定類型,請說:T_ARRAY
比其他類型增加更多,您可能需要查找您永遠要追加的數組。
您可以使用pry-byebug
進入控制台以繞過特定對象,甚至可以查看根目錄中的所有對象:
ObjectSpace.memsize_of(some_object)
ObjectSpace.reachable_objects_from_root
關於ruby開發者博客之一以及SO回答的更多細節。 我喜歡他們的JRuby / VisualVM分析想法。
使用bundle
將gems安裝到本地路徑:
bundle install --path=.gems/
然后你可以找到包含本機代碼的那些:
find .gems/ -name "*.c"
這給了你:(按我的懷疑順序)
OSX有一個有用的開發工具叫做leaks
,可以告訴你它是否在正在運行的進程中找到未引用的內存。 對於識別內存來自Ruby的位置並不是很有用,但有助於識別它何時發生。
首先要測試的是digest-stringbuffer
。 從自述文件中獲取示例並使用gc_tracer添加一些GC日志記錄
require "digest/stringbuffer"
require "gc_tracer"
GC::Tracer.start_logging "gclog.txt"
module Digest
class Prime31 < StringBuffer
def initialize
@prime = 31
end
def finish
result = 0
buffer.unpack("C*").each do |c|
result += (c * @prime)
end
[result & 0xffffffff].pack("N")
end
end
end
讓它運行很多
while true do
a=[]
500.times do |i|
a.push Digest::Prime31.hexdigest( "abc" * (1000 + i) )
end
sleep 1
end
運行示例:
bundle exec ruby ./stringbuffertest.rb &
pid=$!
監視ruby
進程的駐留和虛擬內存大小,以及發現的leaks
計數:
while true; do
ps=$(ps -o rss,vsz -p $pid | tail +2)
leaks=$(leaks $pid | grep -c Leak)
echo "$(date) m[$ps] l[$leaks]"
sleep 15
done
看起來我們已經找到了一些東西:
Tue 26 Aug 2014 18:22:36 BST m[104776 2538288] l[8229]
Tue 26 Aug 2014 18:22:51 BST m[110524 2547504] l[13657]
Tue 26 Aug 2014 18:23:07 BST m[113716 2547504] l[19656]
Tue 26 Aug 2014 18:23:22 BST m[113924 2547504] l[25454]
Tue 26 Aug 2014 18:23:38 BST m[113988 2547504] l[30722]
駐留內存正在增加,泄漏工具正在發現越來越多的未引用內存。 確認GC堆大小,對象計數仍然穩定
tail -f gclog.txt | awk '{ print $1, $3, $4, $7, $13 }
1581853040832 468 183 39171 3247996
1581859846164 468 183 33190 3247996
1584677954974 469 183 39088 3254580
1584678531598 469 183 39088 3254580
1584687986226 469 183 33824 3254580
1587512759786 470 183 39643 3261058
1587513449256 470 183 39643 3261058
1587521726010 470 183 34470 3261058
然后報告問題 。
在我未經訓練的C眼中看來,它們既分配指針又分配緩沖區,但只清理緩沖區 。
看看digest-murmurhash
,它似乎只提供依賴於StringBuffer的函數,所以一旦stringbuffer被修復,泄漏可能會很好。
當他們修補它時,再次測試並移動到下一個寶石。 對於每個gem測試而不是通用示例,最好使用實現中的代碼片段。
第一步是在相同的MRI下在多台機器上證明問題,以排除您已經完成的任何本地操作。
然后在不同的操作系統上嘗試相同的Ruby版本,您也已經完成了。
如果可能的話,嘗試使用JRuby或Rubinius上的代碼。 是否會出現同樣的問題?
如果可能,請嘗試在2.0或1.9上使用相同的代碼,看看是否存在相同的問題。
嘗試從github的頭開發版本,看看是否有任何區別。
如果沒有什么變得明顯,請向 Ruby 提交一個錯誤 ,詳細說明問題以及您已消除的所有內容。 等待開發人員幫忙並提供他們需要的任何東西。 如果您能夠獲得問題設置的最簡潔/最簡單的示例,他們很可能想要重現該問題。 這樣做通常可以幫助您確定問題所在。
我修復內存泄漏並釋放digest / stringbuffer v0.0.3。
https://rubygems.org/gems/digest-stringbuffer
您可以通過v0.0.3再試一次。
我是digest / murmurhash和digest / stringbuffer gems的作者。
實際上,似乎在digest / stringbuffer中有泄漏。
我稍后會解決它。
你能解釋一下代碼嗎?
我建議使用像這樣的單例方法。
Digest::MurmurHash1.hexdigest(some_data)
也許,因為單例方法沒有使用digest / stringbuffer,所以不會泄漏。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.