删除键后,Ruby哈希表出现内存泄漏问题。

5
你好, 我不知道如何在哈希表中删除键后释放内存。当我从哈希表中删除键时,即使手动调用GC.start,内存也不会被释放。这是预期的行为吗?还是当从哈希表中删除键并且这些对象泄漏到其他地方时,GC不会释放内存?我该如何在Ruby中删除哈希表中的键并在内存中取消分配它们?
示例:
irb(main):001:0> `ps -o rss= -p #{Process.pid}`.to_i
=> 4748
irb(main):002:0> a = {}
=> {}
irb(main):003:0> 1000000.times{|i| a[i] = "test #{i}"}
=> 1000000
irb(main):004:0> `ps -o rss= -p #{Process.pid}`.to_i
=> 140340
irb(main):005:0> 1000000.times{|i| a.delete(i)}
=> 1000000
irb(main):006:0> `ps -o rss= -p #{Process.pid}`.to_i
=> 140364
irb(main):007:0> GC.start
=> nil
irb(main):008:0> `ps -o rss= -p #{Process.pid}`.to_i
=> 127076

PS:我使用的是ruby 1.8.7版本。我也尝试过ruby 1.9.2,但效果并不好。


2
我不知道,但我猜测 Ruby 正在池化那些内存以便重复利用,这是一种非常标准的做法。 - falstro
如果有人能够复制Adam的代码,但是展示重复创建和删除对象多次不会增加内存使用量,那将有助于回答这个问题。 - Andrew Grimm
释放的内存:1000000.times{|i| a ||= []; a[i.to_s] = i; @a.delete(i); GC.start; p ObjectSpace.count_objects } - Sergii Mostovyi
4个回答

6

请参考Stackoverflow: How Malloc and Free Work

出于多种良好的原因(在上述引用中详细说明),几乎没有内存管理器将内存释放回操作系统。

要看到进程变化,C部分中的mallocfree需要将内存归还给主机操作系统。这是不可能发生的,但在Ruby级别上,对象已经被垃圾回收并且在解释器中保持在本地的空闲列表中。


3
作为一名资深的开发人员,我使用许多编程语言已经很长时间了,我的想法是:虽然我认为您使用编译语言(如C)的意图是好的,但细粒度的开发人员控制内存管理并不适用于像Ruby、Python和Perl这样的语言。
像Perl、Ruby和Python这样的脚本语言让我们远离内存管理的烦恼。这是我们喜欢它们的原因之一。如果我们有可用的内存,它们将使用所需的内存来完成工作。失去了内存管理控制权是为了加快开发速度和便于调试而做出的权衡;我们没有它,也不需要担心它。如果我需要它,我会使用C或汇编语言。
至于假设它是内存泄漏,我认为这有点天真或傲慢。像您提到的那样的内存泄漏将是一个重大的泄漏,在有如此多基于Ruby的应用程序和站点的情况下,早就有人注意到了。因此,当我看到某些不合理的东西时,我总是先检查自己的代码是否有问题,然后再检查我对某些事物的假设是否正确,如果这些假设仍然看起来正确,我就会寻找其他遇到类似问题的人并看看他们是否有解决方法。而且,如果问题是语言的核心问题,我会深入源代码或与一些核心开发人员交流,并询问自己是否疯了。我以前发现过低级别的错误,但它们都是边角案例,我花了几天时间四处寻找,然后才提到了任何事情,因为我不想像我的同行那样立即向苹果报告错误,然后发现这是他代码中的错误。
总的来说,我认为在解除分配时将内存返回给系统会产生额外的开销,可能会在下一个操作中被撤销,浪费CPU周期,而解释和脚本语言无法承受这种开销,因为它们一开始就不像编译语言那么快。我认为,对于语言来说,假设它需要重复分配大块内存(如果必须这样做),特别是对于像Ruby这样的面向对象语言,保留先前使用的内存是有意义的。
在大局上,考虑到我们通常在计算机中拥有多少可用内存,分配100万个该大小的数组元素并不需要太多内存。我更关注于需要在内存中维护100万个元素,并建议同行认真考虑使用数据库。您可能有一个合理的业务原因来保存所有数据在RAM中。如果是这样,请尽量提高主机上的RAM,那么您应该没问题了。

1

对象应该被垃圾回收。如果您再次创建它们,进程不应显著增长,因为它有所有那些空间。然而,Ruby不会将该内存释放回操作系统,因为它认为未来可能需要那么多的内存。

这是一个相当简单的解释,但基本上,您看到的是正常的。


0

如果您运行两次分配,您可以更多或少地看到@digitalross。如果确实存在这样的内存泄漏,您会期望内存大小翻倍,但事实并非如此。

[~]$ irb                                         rvm:ruby-1.9.3-p0@global 
1.9.3p0 :001 > `ps -o rss= -p #{Process.pid}`.to_i
 => 8148 
1.9.3p0 :002 > a = {}
 => {} 
1.9.3p0 :003 > 1000000.times{|i| a[i] = "test #{i}"}
 => 1000000 
1.9.3p0 :004 > `ps -o rss= -p #{Process.pid}`.to_i
 => 101188 
1.9.3p0 :005 > 1000000.times{|i| a.delete(i)}
 => 1000000 
1.9.3p0 :006 > `ps -o rss= -p #{Process.pid}`.to_i
 => 90960 
1.9.3p0 :007 > GC.start
 => nil 
1.9.3p0 :008 > `ps -o rss= -p #{Process.pid}`.to_i
 => 93388 
1.9.3p0 :009 > `ps -o rss= -p #{Process.pid}`.to_i
 => 93388 
1.9.3p0 :010 > 1000000.times{|i| a[i] = "test #{i}"}
 => 1000000 
1.9.3p0 :011 > `ps -o rss= -p #{Process.pid}`.to_i
 => 140088 
1.9.3p0 :012 > 1000000.times{|i| a.delete(i)}
 => 1000000 
1.9.3p0 :013 > `ps -o rss= -p #{Process.pid}`.to_i
 => 130880 
1.9.3p0 :014 > GC.start
 => nil 
1.9.3p0 :015 > `ps -o rss= -p #{Process.pid}`.to_i
 => 104256 

在第一次运行结束时,该进程报告的内存大小为93388,在第二次运行后,它报告了104256,内存使用量仅增加了约10%。

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