如何处理Ruby 2.1.2内存泄漏?

ruby memory memory-management memory-leaks

2682 观看

3回复

998 作者的声誉

我有一个工作进程,它产生多达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堆大小,然而,进程内存仍在增长。最初我认为它在某些时候停止了增长,但几个小时之后它就超过了极限并且过程崩溃了。

作者: Milovan Zogovic 的来源 发布者: 2014 年 8 月 21 日

回应 (3)


18

32133 作者的声誉

决定

从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你的宝石安装到本地路径:

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

然后报告问题

这似乎很我ç未经训练的眼睛,他们都分配一个指针和一个缓冲,但只清理了缓冲区

看着 digest-murmurhash,似乎只提供依靠StringBuffer的功能,所以一旦StringBuffer的固定泄漏可能会被罚款。

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

测试MRI

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

然后在不同的操作系统上尝试相同的Ruby版本,您也已经完成了。

如果可能的话,尝试使用JRuby或Rubinius上的代码。是否会出现同样的问题?

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

尝试从github的头开发版本,看看是否有任何区别。

如果没有什么变得明显,请 Ruby 提交一个错误,详细说明问题以及您已消除的所有内容。等待开发人员帮忙并提供他们需要的任何东西。如果您能够获得问题设置的最简洁/最简单的示例,他们很可能想要重现该问题。这样做通常可以帮助您确定问题所在。

作者: Matt 发布者: 26.08.2014 06:37

0

11 作者的声誉

我是digest / murmurhash和digest / stringbuffer gems的作者。

实际上,似乎在digest / stringbuffer中有泄漏。

我稍后会解决它。

你能解释一下代码吗?

我建议使用像这样的单例方法。

Digest::MurmurHash1.hexdigest(some_data)

也许,因为单例方法没有使用digest / stringbuffer,所以不会泄漏。

作者: Yuki Kurihara 发布者: 27.08.2014 02:46

1

11 作者的声誉

我修复内存泄漏并释放digest / stringbuffer v0.0.3。

https://rubygems.org/gems/digest-stringbuffer

您可以通过v0.0.3再试一次。

作者: Yuki Kurihara 发布者: 27.08.2014 12:56
32x32