Объекты ActiveRecord в хэшах не собираются с мусором - ошибка или своего рода функция кеширования?
У меня есть простая модель ActiveRecord под названием Student
со 100 записями в таблице. В сеансе консоли rails я делаю следующее:
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.
ОБНОВЛЕНИЕ (РЕШЕНО)
В соответствии с приведенным ниже описанием Оскара Дель Бена в объектном фрагменте кода создается несколько объектов ActiveRecord:: Relation (они фактически созданы в обоих фрагментах кода, но по какой-то причине они "плохо себя ведут" только во втором. пролить свет на почему?). Они поддерживают ссылки на объекты ActiveRecord через переменную экземпляра, называемую @records. Эта переменная экземпляра может быть установлена равной нулю через метод "reset" в ActiveRecord:: Relation. Вы должны выполнить это для всех объектов отношений:
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(используя ruby-mass драгоценный камень Oscar Del Ben), хотя он будет намного медленнее, чем код выше. Обратите внимание, что приведенный выше код не удаляет из памяти несколько объектов ActiveRecord:: Relation. Однако они кажутся довольно незначительными. Вы можете попробовать:
Mass.index(ActiveRecord::Relation)["ActiveRecord::Relation"].each{|x| Mass.detach Mass[x]}
GC.start
И это приведет к удалению некоторых объектов ActiveRecord:: Relation, но не всех из них (не знаю, почему, а те, которые остались, не имеют Mass.references. Weird).
Ответы
Ответ 1
Думаю, я знаю, что происходит. Ruby GC не освобождает неизменяемые объекты (например, символы!). Ключи, возвращаемые group_by, являются неизменяемыми строками, и поэтому они не собираются собирать мусор.
UPDATE
Похоже, проблема не в том, что 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:
Я нашел, что хранят ссылки на объекты. Для этого я использовал рубиновую маску. Оказывается, что отношение 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
по отношению, по-видимому, не помог, но, надеюсь, на этот раз достаточно информации.
Ответ 2
Я не знаю ответа
Но я попытался проверить кучу, как указано на http://blog.headius.com/2010/07/browsing-memory-jruby-way.html
Приложил скриншот к https://skitch.com/deepak_kannan/en3dg/java-visualvm
это была простая программа
class Foo; end
f1 = Foo.new
f2 = Foo.new
GC.start
Затем используется jvisualvm, как указано выше. Выполнял это в irb.
Кажется, что jruby отслеживает область объекта. Объект не получит GC'ed, если есть какие-либо неслабые ссылки на этот объект