我在我的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不理解的事情。
使用的版本:
中的Ubuntu4.0.0进行测试
发布于 2013-12-16 18:24:02
看起来你正在进入迷失的世界。我也不认为问题出在racc
中的c绑定上。
Ruby内存管理既优雅又繁琐。它将对象(名为RVALUE
)存储在大小约为16KB的所谓堆中。在较低的层次上,RVALUE
是一个c-结构,包含不同标准union
对象表示的ruby。
因此,堆存储RVALUE
对象,其大小不超过40个字节。对于String
、Array
、Hash
等对象,这意味着小对象可以放入堆中,但一旦它们达到阈值,就会在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堆数量增加。
希望能有所帮助。
发布于 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上也做了类似的权衡。
发布于 2014-12-05 02:30:27
基于@mudasobwa的解释,我终于找到了原因。CSSPool中的代码正在检查转义序列的超长数据URI。它将使用匹配转义序列或单个字符的正则表达式在URI上调用scan
,将这些结果取消转义,然后将其返回到一个字符串中。这实际上是为URI中的每个字符分配一个字符串。I modified it到gsub
转义序列,这似乎有相同的结果(所有的测试都通过了),并大大减少了结束内存的使用。
使用与最初发布的相同的测试用例(减去Mass.print
输出),这是更改之前的结果:
Memory 12404KB
Memory 292516KB
这是更改后的结果:
Memory 12236KB
Memory 19584KB
https://stackoverflow.com/questions/20385767
复制相似问题