Подтвердить что ты не робот

Объекты 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).

4b9b3361

Ответ 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, если есть какие-либо неслабые ссылки на этот объект