簡體   English   中英

如何處理Ruby 2.1.2內存泄漏?

[英]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值沒有顯着增加。 這表明問題是:

  1. 數據存儲在堆外(字符串,數組等)
  2. 使用本機代碼的gem中的泄漏
  3. Ruby解釋器本身泄漏(最不可能)

值得注意的是,問題出現在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"

這給了你:(按我的懷疑順序)

  • 摘要-的StringBuffer-0.0.2
  • 摘要-murmur哈希-0.3.0
  • 引入nokogiri-1.6.3.1
  • JSON-1.8.1

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

第一步是在相同的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM