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

我在Rails代码中发现了内存泄漏 – 也就是说,我发现了什么代码泄漏,但没有发现泄漏的原因 。 我已经减less到一个不需要Rails的testing用例:

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的CSSparsing器。 /home/jason/big.css是一个1.5MB的CSS文件 。

这输出:

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

你可以看到内存正在上升。 一些计数器上升,但没有特定于CSSPool的对象存在。 我用ruby-mass的“索引”方法来检查引用的对象是这样的:

 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 

结果在这里 。 我不确定这是否证实了C级漏洞,因为我也读过Ruby处理Valgrind不理解的内存。

使用的版本:

  • Ruby 2.0.0-p247(这是我的Rails应用程序运行)
  • Ruby 1.9.3-p392-ref(用于testingruby质量)
  • ruby质量0.1.3
  • CSSPool 4.0.0从这里
  • CentOS 6.4和Ubuntu 13.10

看起来你在这里进入失落的世界 。 我不认为这个问题与racc c-binding racc

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

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

这个额外的内存是灵活的; 只要一个对象变成了GC就会被释放。 这就是为什么你的big_stringtesting用例显示内存上下行为:

 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] )本身并没有被释放回操作系统,一旦获得。 看,我会对你的testing用例做一个单调的改变:

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

而且,voilá:

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

内存不会释放回OS,因为我介绍的arrays的每个元素都适合 RVALUE大小, 并存储在ruby堆中。

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

总结:我不认为, c代码泄漏。 我认为这个问题是在你的css巨大的形象base64表示。 我不知道parsing器里面发生了什么,但是看起来像一个巨大的string迫使ruby堆数增加。

希望能帮助到你。

好的,我find了答案。 我要离开我的另一个答案,因为这个信息很难收集,是相关的,它可以帮助别人寻找相关的问题。

但是,您的问题似乎是由于Ruby实际上并没有在获取操作系统后将内存释放回操作系统。

内存分配

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

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

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

我释放了很多记忆,为什么我的过程仍然如此之大?

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

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

因此,在用户空间需要向内核请求更多内存之前,可能会发生多个用户空间内存分配。 因此,如果从内核获得大量内存并仅使用其中的一小部分内存,则仍会分配大量内存。

将内存释放回内核也是有代价的。 用户空间内存分配器可以保存在内存中(私下),希望在同一个进程中可以重用,而不是把它交给内核用于其他进程。 (Ruby最佳实践)

所以,你的对象可能已经被垃圾收集并且释放回到Ruby的可用内存中,但是因为Ruby从未将未使用的内存返回给操作系统,所以即使在垃圾收集之后,该进程的rss值也保持不变。 这实际上是通过devise。 据迈克·佩勒姆说 :

…因为MRI永远不会回馈未使用的内存,所以当它只使用100-200时,我们的守护进程可以轻松地占用300-400MB。

重要的是要注意,这基本上是通过devise。 Ruby的历史大多是作为文本处理的命令行工具,因此它重视快速启动和小内存占用。 它不是为长时间运行的守护进程/服务器进程而devise的。 Java在其客户端和服务器虚拟机中做出了类似的折衷。

这可能是由于Ruby 1.9.3及以上版本中的“懒惰清理”function。

懒惰扫除基本上意味着,在垃圾收集过程中,Ruby只会“扫除”足够的对象,为需要创build的新对象创build空间。 这是因为在Ruby垃圾回收器运行的时候,没有其他的东西。 这被称为“阻止世界”垃圾收集。

本质上,懒惰的清扫减less了Ruby需要“停止世界”的时间。 你可以在这里阅读更多关于偷懒的信息 。

你的RUBY_GC_MALLOC_LIMIT环境variables是什么样的?

以下是Sam Saffron关于懒惰扫描和RUBY_GC_MALLOC_LIMIT 博客的摘录:

Ruby 2.0中的GC有两种不同的风格。 我们有一个“完整的”GC,在我们分配超过我们的malloc_limit和一个懒惰的扫描(部分GC)之后运行,如果我们的堆中没有空闲槽,就会运行。

惰性扫描比完整的GC需要更less的时间,但是只执行部分GC。 它的目标是更频繁地执行一个短的GC,从而提高整体吞吐量。 世界停了下来,但时间不多了。

malloc_limit被设置为8MB,你可以通过设置RUBY_GC_MALLOC_LIMIT来提高它。

您的RUBY_GC_MALLOC_LIMIT是否过高? 我的设置为100000000(100MB)。 默认是8MB左右,但对于Rails应用程序,他们build议它相当高一点。 如果你太高,可能会阻止Ruby删除垃圾对象,因为它认为它有很大的增长空间。

基于@ mudasobwa的解释,我终于find了原因。 CSSPool中的代码正在检查转义序列的非常长的数据URI。 它会调用scan URI的正则expression式匹配转义序列或单个字符, map这些结果map到unescape,然后joinjoin到一个string中。 这有效地为URI中的每个字符分配一个string。 我修改它为gsub转义序列,这似乎有相同的结果(所有的testing通过),并大大减less了使用的结束内存。

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

 Memory 12404KB Memory 292516KB 

这是变化后的结果:

 Memory 12236KB Memory 19584KB