gpt4 book ai didi

ruby - 散列中的 ActiveRecord 对象未被垃圾收集——一个错误或某种缓存功能?

转载 作者:数据小太阳 更新时间:2023-10-29 06:26:38 26 4
gpt4 key购买 nike

我有一个名为 Student 的简单 ActiveRecord 模型,表中有 100 条记录。我在 Rails 控制台 session 中执行以下操作:

ObjectSpace.each_object(ActiveRecord::Base).count
# => 0

x = Student.all

ObjectSpace.each_object(ActiveRecord::Base).count
# => 100

x = nil
GC.start

ObjectSpace.each_object(ActiveRecord::Base).count
# => 0 # Good!

现在我执行以下操作:

ObjectSpace.each_object(ActiveRecord::Base).count
# => 0

x = Student.all.group_by(&:last_name)

ObjectSpace.each_object(ActiveRecord::Base).count
# => 100

x = nil
GC.start

ObjectSpace.each_object(ActiveRecord::Base).count
# => 100 # Bad!

谁能解释为什么会发生这种情况,以及是否有一种聪明的方法可以在不知道底层哈希结构的情况下解决这个问题?我知道我可以做到这一点:

x.keys.each{|k| x[k]=nil}
x = nil
GC.start

它会正确地从内存中删除所有 Student 对象,但我想知道是否有一个通用的解决方案(我的现实生活中的问题广泛存在并且具有比上面显示的哈希更复杂的数据结构)。

我正在使用 Ruby 1.9.3-p0 和 Rails 3.1.0。

更新(已解决)

根据下面 Oscar Del Ben 的解释,一些 ActiveRecord::Relation 对象是在有问题的代码片段中创建的(它们实际上是在两个代码片段中创建的,但出于某种原因,它们仅在第二个代码片段中“行为不端”。有人可以阐明为什么?)。它们通过名为@records 的实例变量维护对 ActiveRecord 对象的引用。这个实例变量可以通过 ActiveRecord::Relation 上的“reset”方法设置为 nil。您必须确保对所有关系对象执行此操作:

ObjectSpace.each_object(ActiveRecord::Base).count
# => 100

ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset)

GC.start
ObjectSpace.each_object(ActiveRecord::Base).count
# => 0

注意:您也可以使用 Mass.detach(使用 Oscar Del Ben 引用的 ruby-mass gem),尽管它会比上面的代码慢得多。请注意,上面的代码并没有从内存中删除一些 ActiveRecord::Relation 对象。虽然这些看起来微不足道。你可以尝试这样做:

Mass.index(ActiveRecord::Relation)["ActiveRecord::Relation"].each{|x| Mass.detach Mass[x]}
GC.start

这会删除一些 ActiveRecord::Relation 对象,但不是全部(不知道为什么,剩下的那些没有 Mass.references。很奇怪)。

最佳答案

我想我知道发生了什么。 Ruby 的 GC 不会释放不可变对象(immutable对象)(比如符号!)。 group_by 返回的键是不可变字符串,因此不会被垃圾回收。

更新:

看来问题不在于 Rails 本身。我尝试单独使用 group_by,有时对象不会被垃圾回收:

oscardelben~/% irb
irb(main):001:0> class Foo
irb(main):002:1> end
=> nil
irb(main):003:0> {"1" => Foo.new, "2" => Foo.new}
=> {"1"=>#<Foo:0x007f9efd8072a0>, "2"=>#<Foo:0x007f9efd807250>}
irb(main):004:0> ObjectSpace.each_object(Foo).count
=> 2
irb(main):005:0> GC.start
=> nil
irb(main):006:0> ObjectSpace.each_object(Foo).count
=> 0
irb(main):007:0> {"1" => Foo.new, "2" => Foo.new}.group_by
=> #<Enumerator: {"1"=>#<Foo:0x007f9efb83d0c8>, "2"=>#<Foo:0x007f9efb83d078>}:group_by>
irb(main):008:0> GC.start
=> nil
irb(main):009:0> ObjectSpace.each_object(Foo).count
=> 2 # Not garbage collected
irb(main):010:0> GC.start
=> nil
irb(main):011:0> ObjectSpace.each_object(Foo).count
=> 0 # Garbage collected

我已经深入了解了 GC 的内部结构(非常容易理解),这似乎是一个范围问题。 Ruby 遍历当前范围内的所有对象并标记它认为仍在使用的对象,之后它遍历堆中的所有对象并释放那些未使用的对象 已标记。

在这种情况下,我认为哈希仍然被标记,即使它超出范围。发生这种情况的原因有很多。我会继续调查。

更新 2:

我找到了保持对象引用的原因。为此,我使用了 ruby mass gem 。事实证明,Active Record 关系跟踪返回的对象。

User.limit(1).group_by(&:name)
GC.start
ObjectSpace.each_object(ActiveRecord::Base).each do |obj|
p Mass.references obj # {"ActiveRecord::Relation#70247565268860"=>["@records"]}
end

不幸的是,调用关系上的 reset 似乎没有帮助,但希望现在这些信息已经足够了。

关于ruby - 散列中的 ActiveRecord 对象未被垃圾收集——一个错误或某种缓存功能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11149837/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com