首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >查找Ruby中内存泄漏的原因

查找Ruby中内存泄漏的原因
EN

Stack Overflow用户
提问于 2013-12-05 04:57:17
回答 4查看 15.5K关注 0票数 59

我在我的Rails代码中发现了一个内存泄漏-也就是说,我已经找到了泄漏的代码,但不知道它为什么泄漏。我已经将其简化为不需要Rails的测试用例:

require 'csspool'
require 'ruby-mass'

def report
    puts 'Memory ' + `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)[1].to_s + 'KB'
    Mass.print
end

report

# note I do not store the return value here
CSSPool::CSS::Document.parse(File.new('/home/jason/big.css'))

ObjectSpace.garbage_collect
sleep 1

report

ruby-mass应该可以让我看到内存中的所有对象。CSSPool是一个基于racc的CSS解析器。/home/jason/big.css为a 1.5MB CSS file

这将输出以下内容:

Memory 9264KB

==================================================
 Objects within [] namespace
==================================================
  String: 7261
  RubyVM::InstructionSequence: 1151
  Array: 562
  Class: 313
  Regexp: 181
  Proc: 111
  Encoding: 99
  Gem::StubSpecification: 66
  Gem::StubSpecification::StubLine: 60
  Gem::Version: 60
  Module: 31
  Hash: 29
  Gem::Requirement: 25
  RubyVM::Env: 11
  Gem::Specification: 8
  Float: 7
  Gem::Dependency: 7
  Range: 4
  Bignum: 3
  IO: 3
  Mutex: 3
  Time: 3
  Object: 2
  ARGF.class: 1
  Binding: 1
  Complex: 1
  Data: 1
  Gem::PathSupport: 1
  IOError: 1
  MatchData: 1
  Monitor: 1
  NoMemoryError: 1
  Process::Status: 1
  Random: 1
  RubyVM: 1
  SystemStackError: 1
  Thread: 1
  ThreadGroup: 1
  fatal: 1
==================================================

Memory 258860KB

==================================================
 Objects within [] namespace
==================================================
  String: 7456
  RubyVM::InstructionSequence: 1151
  Array: 564
  Class: 313
  Regexp: 181
  Proc: 113
  Encoding: 99
  Gem::StubSpecification: 66
  Gem::StubSpecification::StubLine: 60
  Gem::Version: 60
  Module: 31
  Hash: 30
  Gem::Requirement: 25
  RubyVM::Env: 13
  Gem::Specification: 8
  Float: 7
  Gem::Dependency: 7
  Range: 4
  Bignum: 3
  IO: 3
  Mutex: 3
  Time: 3
  Object: 2
  ARGF.class: 1
  Binding: 1
  Complex: 1
  Data: 1
  Gem::PathSupport: 1
  IOError: 1
  MatchData: 1
  Monitor: 1
  NoMemoryError: 1
  Process::Status: 1
  Random: 1
  RubyVM: 1
  SystemStackError: 1
  Thread: 1
  ThreadGroup: 1
  fatal: 1
==================================================

你可以看到memory way up。一些计数器会上升,但不存在特定于CSSPool的对象。我使用ruby- objects的"index“方法来检查具有如下引用的对象:

Mass.index.each do |k,v|
    v.each do |id|
        refs = Mass.references(Mass[id])
        puts refs if !refs.empty?
    end
end

但是再说一次,这并没有给我任何与CSSPool相关的东西,只有gem信息之类的。

我也尝试过输出"GC.stat"...

puts GC.stat
CSSPool::CSS::Document.parse(File.new('/home/jason/big.css'))
ObjectSpace.garbage_collect
sleep 1
puts GC.stat

结果:

{:count=>4, :heap_used=>126, :heap_length=>138, :heap_increment=>12, :heap_live_num=>50924, :heap_free_num=>24595, :heap_final_num=>0, :total_allocated_object=>86030, :total_freed_object=>35106}
{:count=>16, :heap_used=>6039, :heap_length=>12933, :heap_increment=>3841, :heap_live_num=>13369, :heap_free_num=>2443302, :heap_final_num=>0, :total_allocated_object=>3771675, :total_freed_object=>3758306}

据我所知,如果一个对象没有被引用,并且发生了垃圾回收,那么这个对象应该从内存中清除。但这似乎不是这里正在发生的事情。

我也读过关于C级内存泄漏的文章,因为CSSPool使用使用C代码的Racc,所以我认为这是一种可能性。我已经通过Valgrind运行了我的代码:

valgrind --partial-loads-ok=yes --undef-value-errors=no --leak-check=full --fullpath-after= ruby leak.rb 2> valgrind.txt

结果是here。我不确定这是否证实了C级的泄漏,因为我也读到过Ruby使用内存做Valgrind不理解的事情。

使用的版本:

  • Ruby 2.0.0-p247 (这是我的Rails应用程序运行的代码)
  • Ruby 1.9.3-p392-ref (用于使用Ubuntu6.4和Ubuntu13.10

中的Ubuntu4.0.0进行测试

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2013-12-16 18:24:02

看起来你正在进入迷失的世界。我也不认为问题出在racc中的c绑定上。

Ruby内存管理既优雅又繁琐。它将对象(名为RVALUE)存储在大小约为16KB的所谓堆中。在较低的层次上,RVALUE是一个c-结构,包含不同标准union对象表示的ruby。

因此,堆存储RVALUE对象,其大小不超过40个字节。对于StringArrayHash等对象,这意味着小对象可以放入堆中,但一旦它们达到阈值,就会在Ruby堆之外分配额外的内存。

这个额外的内存是灵活的;一旦对象被GC,它就会被释放。这就是为什么你用big_string的测试用例显示内存上下行为的原因:

def report
  puts 'Memory ' + `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`
          .strip.split.map(&:to_i)[1].to_s + 'KB'
end
report
big_var = " " * 10000000
report
big_var = nil 
report
ObjectSpace.garbage_collect
sleep 1
report
# ⇒ Memory 11788KB
# ⇒ Memory 65188KB
# ⇒ Memory 65188KB
# ⇒ Memory 11788KB

但是,一旦获得堆(参见GC[:heap_length])本身,就不会被释放并返回给操作系统。听着,我会对你的测试用例做一个简单的修改:

- big_var = " " * 10000000
+ big_var = 1_000_000.times.map(&:to_s)

然后,瞧,́:

# ⇒ Memory 11788KB
# ⇒ Memory 65188KB
# ⇒ Memory 65188KB
# ⇒ Memory 57448KB

内存不再释放回OS,因为我引入的数组中的每个元素都适合RVALUE大小,并存储在ruby堆中。

如果您在运行GC之后检查GC.stat的输出,您会发现GC[:heap_used]值如预期的那样减少了。Ruby现在有很多空堆,准备好了。

总结:我不认为,c代码泄露了。我认为问题出在css中巨大图像的base64表示中。我不知道解析器内部发生了什么,但看起来巨大的字符串迫使ruby堆数量增加。

希望能有所帮助。

票数 41
EN

Stack Overflow用户

发布于 2013-12-05 13:56:53

好吧,我找到答案了。我留下了我的另一个答案,因为这些信息很难收集,它是相关的,它可以帮助其他人搜索相关的问题。

然而,你的问题似乎是由于这样一个事实,即一旦获得内存,Ruby实际上并没有释放回操作系统。

内存分配

虽然Ruby程序员通常不会担心内存分配问题,但有时会出现以下问题:

为什么我的Ruby进程在我清除了所有对大对象的引用后仍然保持这么大?我确信/确定/ GC已经运行了几次,并释放了我的大对象,并且我没有泄漏内存。

C程序员可能会问同样的问题:

我释放()-ed了很多内存,为什么我的进程仍然这么大?

从内核到用户空间的内存分配以大块的形式更便宜,因此用户空间通过自己做更多的工作来避免与内核的交互。

用户空间库/运行时实现了一个内存分配器(例如:libc中的malloc(3) ),它占用大量的内核memory2,并将它们分成较小的块供用户空间应用程序使用。

因此,在用户空间需要向内核请求更多内存之前,可能会发生几次用户空间内存分配。因此,如果你从内核获得了一大块内存,并且只使用了其中的一小部分,那么这一大块内存仍然会被分配。

将内存释放回内核也是有代价的。用户空间内存分配器可能会(私下)保留该内存,希望它可以在同一进程中重用,而不是将其返回给内核在其他进程中使用。(Ruby Best Practices)

因此,您的对象很可能已经被垃圾收集并释放回Ruby的可用内存,但是因为Ruby从不将未使用的内存返回给操作系统,所以进程的RSS值保持不变,即使在垃圾收集之后也是如此。这实际上是设计出来的。根据Mike Perham的说法

...And由于MRI从不退还未使用的内存,所以当它只使用100-200MB时,我们的守护进程可以很容易地占用300-400MB。

重要的是要注意,这本质上是设计出来的。Ruby的历史主要是作为文本处理的命令行工具,因此它重视快速启动和较小的内存占用。它不是为长时间运行的守护进程/服务器进程设计的。Java在客户端和服务器VM上也做了类似的权衡。

票数 15
EN

Stack Overflow用户

发布于 2014-12-05 02:30:27

基于@mudasobwa的解释,我终于找到了原因。CSSPool中的代码正在检查转义序列的超长数据URI。它将使用匹配转义序列或单个字符的正则表达式在URI上调用scan,将这些结果取消转义,然后将其返回到一个字符串中。这实际上是为URI中的每个字符分配一个字符串。I modified itgsub转义序列,这似乎有相同的结果(所有的测试都通过了),并大大减少了结束内存的使用。

使用与最初发布的相同的测试用例(减去Mass.print输出),这是更改之前的结果:

Memory 12404KB
Memory 292516KB

这是更改后的结果:

Memory 12236KB
Memory 19584KB
票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20385767

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档