Ruby 编程/参考/对象/GC
Ruby 使用自动垃圾回收。
MRI 的 GC 是“全标记清除”,并在它用完内存槽时运行(即在添加更多内存之前,它会扫描现有的内存,看看是否可以先释放一些内存——如果没有,它会添加更多内存)。它也会在扩展分配了 GC_MALLOC_LIMIT 字节后被触发。不幸的是,这会导致对所有内存进行遍历,这通常很慢。请参阅 良好的描述。
GC 众所周知通常会占用 10% 的 CPU,但如果你有很大的内存负载,它可能会占用更多。
GC 可以在“编译时”(MRI/KRI 的 < 1.9)http://blog.evanweaver.com/articles/2009/04/09/ruby-gc-tuning 进行调优,或者可以使用环境变量(REE、MRI/KRI 的 >= 1.9)进行调优。
一些提示
你可以将编译器变量 GC_MALLOC_LIMIT 设置为一个非常高的值,这会导致你的程序使用更多内存,但更少地遍历它。适用于大型应用,例如 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
- 如果你写了“更多”内存,它可能会清除堆栈中旧的引用。
"here":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 并且你的应用使用大量内存时(而 MRI 几乎从不将内存归还给系统),你会遇到性能损失。解决方法
- 通过分配更少的对象来减少内存使用
- 在 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 等)。