如何在我的Ruby代码中追踪内存泄漏?

32

问题

我正在调试一个rake任务中的内存泄漏。 我想看到以下内容的调用堆栈:

  • 存在的对象
  • 最初分配这些对象的对象或行

使用ruby-prof可以实现吗?

如果不行,我应该使用什么工具?

设置

Gems

Rake任务

  • 使用数据加载文件和Active记录对象将CSV文件直接导入MySQL数据库。

我尝试过的方法

我尝试了以下模式:

  • RubyProf::ALLOCATIONS
  • RubyProf::MEMORY

文档中只是说:

RubyProf::ALLOCATIONS 对象分配报告显示程序中每个方法分配的对象数量。

RubyProf::MEMORY 内存使用报告显示程序中每个方法使用的内存量。

这意味着ruby-prof只报告了对象的总分配情况,而不仅仅是存活的对象。

我尝试了Ruby-MassBloat Check,但是两者都似乎无法实现我的要求。 Ruby-Mass还会因为某种原因在内存中找到FactoryGirl对象而崩溃。


1
第一个问题:您是否正在使用任何已知具有泄漏问题的 gem,例如 ImageMagick / RMagick? - tadman
我在rake任务中使用EventBus和activerecord-fast-import。我还在运行Rails 3.2.16版本。我安装了很多其他的gem,但没有在rake任务中使用它们。 - John Gallagher
你可以尝试偶尔调用GC.start来释放一些内存。这会使你的应用程序变慢,但内存占用通常会大大降低。 - tadman
@tadman 我已经做了这个很多次 - 它可以稍微减少一点内存,但似乎仍然在某些地方占用了很多内存... - John Gallagher
4个回答

39
我发现在定位内存泄漏时,ruby-prof并不是很有用,因为你需要一个打了补丁的Ruby解释器。在Ruby 2.1中,跟踪对象分配变得更加容易了。也许探索这个问题自己处理是最好的选择。
我推荐Ruby核心开发人员之一tmml的博客文章Ruby 2.1: objspace.so。基本上,在调试应用程序时,您可以获取大量信息。
ObjectSpace.each_object{ |o| ... }
ObjectSpace.count_objects #=> {:TOTAL=>55298, :FREE=>10289, :T_OBJECT=>3371, ...}

require 'objspace'
ObjectSpace.memsize_of(o) #=> 0 /* additional bytes allocated by object */
ObjectSpace.count_tdata_objects #=> {Encoding=>100, Time=>87, RubyVM::Env=>17, ...}
ObjectSpace.count_nodes #=> {:NODE_SCOPE=>2, :NODE_BLOCK=>688, :NODE_IF=>9, ...}
ObjectSpace.reachable_objects_from(o) #=> [referenced, objects, ...]
ObjectSpace.reachable_objects_from_root #=> {"symbols"=>..., "global_tbl"=>...} /* in 2.1 */

使用 Ruby 2.1,甚至可以开始跟踪新对象的分配并收集有关每个新对象的元数据:
require 'objspace'
ObjectSpace.trace_object_allocations_start

class MyApp
  def perform
    "foobar"
  end
end

o = MyApp.new.perform
ObjectSpace.allocation_sourcefile(o) #=> "example.rb"
ObjectSpace.allocation_sourceline(o) #=> 6
ObjectSpace.allocation_generation(o) #=> 1
ObjectSpace.allocation_class_path(o) #=> "MyApp"
ObjectSpace.allocation_method_id(o)  #=> :perform

请使用prypry-byebug开始探索内存堆,分别尝试在代码中的不同段落中进行。在Ruby 2.1之前,我总是依赖于ObjectSpace.count_objects并计算结果的差异,以查看特定对象类型是否增长。

当增长的对象数量在迭代期间重新测试回较小的数量时,垃圾收集器可以正常工作,而不是继续增长。无论如何,垃圾收集器都应该一直运行,您可以通过查看Garbage Collector statistics.来确认自己。

根据我的经验,这要么是字符串,要么是符号(T_STRING)。在ruby 2.2.0之前,符号不会被垃圾回收,因此请确保您的CSV或其中的部分在转换过程中没有转换为符号。

如果您感到不适,请尝试使用JRuby在JVM上运行代码。至少,像VisualVM这样的工具更好地支持内存分析。

2
顺便说一下,我也在博客中写了这个问题的解决方法:http://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby - Sam Saffron
2
TIL:符号不会被垃圾收集。 - Nakilon
2
符号从Ruby 2.2开始进行垃圾回收。 - mfink

5

5

3

有一个名为ruby-mass的宝石(gem),它可以在ObjectSpace上提供良好的API。

解决这个问题的一种方法是在完成对象后检查引用。

object = ...
# more logic
puts Mass.references(object)

如果存在至少一个引用,该对象将不会被垃圾回收,您需要想办法去除该引用。例如:

object.instance_variable_set("@example", nil)

# or

ObjectSpace.each_object(Your::Object::Class::Name).each do |obj|
  obj.instance_variable_set("@example", nil)
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接