跳转到内容

Ruby 编程/参考/对象/GC

来自维基教科书,开放的书籍,为开放的世界

Ruby 具有自动垃圾回收功能。


调整 GC

[编辑 | 编辑源代码]

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
  • 如果你编写“更多”内存,它可能会清除堆栈上的旧引用。

调整 Jruby 的 GC。

[编辑 | 编辑源代码]

"这里":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 返回系统)。解决方法

  1. 通过分配更少的对象来减少 RAM 使用量
  2. 在 fork 的“子进程”中执行工作,该子进程将返回所需的值。子进程将死亡,释放其内存。
  3. 使用 Jruby 等(jruby 有一个优秀的 GC,即使在大型应用程序中也不会降低速度)。
  4. 使用允许使用本地类型的 gem,例如 NArray 或 RubyGoogle Hash。
  5. 使用 REE 而不是 1.8.6(因为它包含使 GC 更高效的 MBARI 补丁)。
  6. 使用 1.9.x 而不是 1.8.6(因为它使用真正的线程,堆栈上的鬼引用要少得多,因此使 GC 更高效)。
  7. 将你的应用程序设置为定期重启(passenger 可以做到这一点)。
  8. 创建多个应用程序,一个设计为大型且缓慢,其他应用程序敏捷(将你的 GC 密集型操作全部运行在大型应用程序中)。
  9. 自己调用 GC.start,或将其与 GC.disable 混合使用
  10. 使用 memprof gem 查看泄漏发生的位置(或 dike gem 或类似的 gem)。
华夏公益教科书