如何处理Ruby 2.1.2内存泄漏?

我有一个工作进程生成多达50个线程,并执行一些异步操作(大多数是http调用).当我启动该过程时,它将从大约35MB的内存开始,并迅速增长到250MB.从那时起,它越来越多,问题是记忆永远不会停止增长(尽管增长阶段随着时间的推移而下降).几天之后,进程只会延长可用内存并崩溃.

我做了很多分析和剖析,似乎找不到什么问题.进程内存不断增长,即使堆大小几乎不变.我收集了GC.stat输出到您可以访问的电子表格:

https://docs.google.com/spreadsheets/d/17TohDNXQ_MXM31CeAmR2ptHFYfvOeF3dB6WCBkBS_Bc/edit?usp=sharing

即使进程内存似乎终于稳定在415MB,但是在接下来的几天内它将继续增长,直到达到512MB限制并且崩溃.

我也尝试使用objectspace跟踪对象,但跟踪对象的内存总和从未跨越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建议的内容,现在没有漏洞识别的内存泄漏,随着时间的推移,ruby堆大小没有增加,但是进程内存仍在增长.最初我以为它在某个时候停止了增长,但是几个小时后,它超出了限制和进程的崩溃.

解决方法

从您的GC日志中,似乎问题不是ruby对象引用泄漏,因为heap_live_slot值不会显着增加.这将表明问题是:

>存储在堆外的数据(字符串,数组等)
>使用本机代码的gem中的泄漏
> Ruby解释器本身泄漏(最不可能)

有趣的是,OSX和Heroku(Ubuntu Linux)上都出现了这个问题.

对象数据和“堆”

Ruby 2.1 garbage collection仅对包含少量数据的对象使用报告的“堆”.当Object中包含的数据超过某个限制时,数据被移动并分配给堆外部的区域.您可以使用ObjectSpace获取每种数据类型的总体大小:

require 'objspace'
ObjectSpace.count_objects_size({})

与GC统计信息一起收集可能会指示在堆外部分配内存的位置.如果你找到一个特定的类型,说:T_ARRAY比其他人更多,你可能需要寻找一个你永远追加的数组.

您可以使用pry-byebug放入控制台来绕过特定对象,甚至查看根目录中的所有对象:

ObjectSpace.memsize_of(some_object)
ObjectSpace.reachable_objects_from_root

ruby developers blogthis SO answer之间有一个更详细的信息.我喜欢他们的JRuby / VisualVM分析思想.

测试本地宝石

使用bundle将宝石安装到本地路径中:

bundle install --path=.gems/

然后,您可以找到包含本机代码的那些:

find .gems/ -name "*.c"

哪个给你:(按我的怀疑顺序)

digest-stringbuffer-0.0.2
digest-murmurhash-0.3.0
> nokogiri-1.6.3.1
> json-1.8.1

OSX有一个名为leaks的有用的开发工具,可以告诉您是否在正在运行的进程中找到未引用的内存.在Ruby中识别内存的位置并不是非常有用,但有助于确定何时发生.

首先要测试的是digest-stringbuffer.从Readme获取示例,并添加一些GC日志记录与gc_tracer

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=$!

监视红宝石进程的驻留和虚拟内存大小以及确定的泄漏计数:

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

然后报issue.

在我未经训练的C眼中,他们分配了pointerbuffer,但只清理了buffer.

看看digest-murmurhash,似乎只提供依赖StringBuffer的函数,所以一旦stringbuffer被修复就会泄漏.

当他们修补它,再次测试并移动到下一个宝石.对于每个宝石测试,最好使用实现中的代码片段,而不是通用示例.

测试MRI

第一步是在同一MRI下的多台机器上证明这个问题,以排除您已经完成的任何本地的任何操作.

然后在不同的操作系统上尝试使用相同的Ruby版本,这也是您所做的.

尝试JRuby或Rubinius上的代码,如果可能的话.同样的问题是否发生?

如果可能,请尝试使用相同的代码2.0或1.9,看看是否存在同样的问题.

尝试从github的头开发版本,看看是否有所作为.

如果没有什么变得明显,那么Ruby的submit a bug将详细介绍这个问题以及所有消除的事情.等待开发人员帮助和提供他们需要的任何东西.他们很有可能想要重现这个问题,所以如果你能够得到最简洁/最小化的问题设置示例.这样做通常可以帮助您确定问题是什么.

相关文章

以下代码导致我的问题: class Foo def initialize(n=0) @n = n end attr_accessor :n d...
这是我的spec文件,当为上下文添加测试“而不是可单独更新用户余额”时,我得到以下错误. require 's...
我有一个拦截器:DevelopmentMailInterceptor和一个启动拦截器的inititializer setup_mail.rb. 但我想将...
例如,如果我有YAML文件 en: questions: new: 'New Question' other: recent: ...
我听说在RSpec中避免它,let,let !,指定,之前和主题是最佳做法. 关于让,让!之前,如果不使用这些,我该如...
我在Rails中使用MongoDB和mongo_mapper gem,项目足够大.有什么办法可以将数据从Mongoid迁移到 Postgres...