Ruby 编程/参考/对象/GC
Ruby 具有自动垃圾回收功能。
MRI 的 GC 是“完全标记和清除”,并在内存槽耗尽时运行(即在添加更多内存之前,它会扫描现有的内存,看看是否可以先释放一些 - 如果不能,它会添加更多内存)。它也会在扩展分配 GC_MALLOC_LIMIT 字节后触发。不幸的是,这会导致对所有内存的遍历,通常很慢。查看 良好描述。
众所周知,GC 通常会占用 10% 的 CPU,但如果你的 RAM 负载很大,它可能会占用更多。
GC 可以“编译时”调整(MRI/KRI 的 < 1.9)http://blog.evanweaver.com/articles/2009/04/09/ruby-gc-tuning 或使用环境变量调整(REE、MRI/KRI 的 >= 1.9)。
一些提示
你可以将编译器变量 GC_MALLOC_LIMIT 设置为一个非常高的值,这会导致你的程序使用更多 RAM,但更少地遍历它。适用于大型应用程序,例如 rails。
你可以使用 jruby/rubinius,它们使用更复杂的 GC。
你可以使用“本地”库,它们将值存储起来,这样 Ruby 就不用跟踪它们并收集它们。示例:“NArray” gem 和“google_hash” gem。
要关闭它:@GC.disable@
要强制它运行一次:@GC.start@
Ruby(MRI)的 GC 是标记和清除,这意味着它是保守的。为了实现这一点,它会遍历堆栈,查找任何“看起来”像对现有 ruby 对象的引用的内存部分,并将它们标记为活动状态。这会导致误报,即使没有对对象的引用剩余。
这个问题在 1.8.x 系列中尤其严重,当他们没有应用 MBARI 补丁时(大多数没有,REE 有)。这是因为,当使用线程时,它实际上会为每个线程分配堆栈的完整副本,并且当线程运行时,它们的堆栈被复制到“真实”堆栈,它们可以拾取属于其他线程的鬼引用,以及因为 1.8 MRI 解释器包含巨大的 switch 语句,它们会留下大量未触碰的堆栈内存,因此它可以继续错误地包含对“鬼”引用的引用。
这意味着如果你调用 GC.start,它不能 *保证* 收集任何东西。
一些提示
- 如果你从方法内部调用你的代码,并在该方法 *退出*,它可能会更容易地收集它。
- 你可以使用 ensure 块进行自己的 GC,例如
a = SomeClass.new begin ... ensure a.cleanup end
- 如果你编写“更多”内存,它可能会清除堆栈上的旧引用。
"这里":http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/27550 是一个调整 Jruby 的 GC 的示例。G1GC 理论上是一个“永不暂停”的 GC,尽管实际上大多数 GC 在速度方面都相当出色。对于长时间运行的应用程序,你可能需要在服务器模式下运行(--server),以提高性能,尽管启动时间会减少。
据说 Rubinius 也拥有更好的 GC。
由于 MRI 的 GC 本质上是 O(N),随着它的增长,你在 GC 发生时会遇到性能损失,并且你的应用程序正在使用大量的 RAM(并且 MRI 几乎从未将它的 RAM 返回系统)。解决方法
- 通过分配更少的对象来减少 RAM 使用量
- 在 fork 的“子进程”中执行工作,该子进程将返回所需的值。子进程将死亡,释放其内存。
- 使用 Jruby 等(jruby 有一个优秀的 GC,即使在大型应用程序中也不会降低速度)。
- 使用允许使用本地类型的 gem,例如 NArray 或 RubyGoogle Hash。
- 使用 REE 而不是 1.8.6(因为它包含使 GC 更高效的 MBARI 补丁)。
- 使用 1.9.x 而不是 1.8.6(因为它使用真正的线程,堆栈上的鬼引用要少得多,因此使 GC 更高效)。
- 将你的应用程序设置为定期重启(passenger 可以做到这一点)。
- 创建多个应用程序,一个设计为大型且缓慢,其他应用程序敏捷(将你的 GC 密集型操作全部运行在大型应用程序中)。
- 自己调用 GC.start,或将其与 GC.disable 混合使用
- 使用 memprof gem 查看泄漏发生的位置(或 dike gem 或类似的 gem)。