Для любопытных : Оказывается, моя утечка памяти не имела ничего общего с тем, что я здесь добавил в пример. Я думал, что проблема была решена до некоторого примера кода, но у моего примера кода были другие проблемы. Я действительно обнаружил, что моя реальная проблема, и вот она: Ruby Symbol # to_proc пропускает ссылки в 1.9.2-p180?
У меня есть два класса ruby (Generator
и Member
, в этом примере), где Генератор служит фабрикой (в произвольном определении термина) объектов-членов, и каждый член содержит ссылку на Генератор, который построил его.
Код:
class Generator
def new_member
Member.new
end
end
class Member
attr_reader :generator
def self.get(generator)
@generator = generator
puts "Provided generator: #{generator}"
generator.new_member
end
end
Используя IRB, я ожидал бы, что если я просто позвоню, я просто вызову Member.get(Generator.new)
, но на самом деле не присваиваю результат чему-либо, как ссылку на вновь созданный объект Generator
, так и на вновь созданный Member
объект должен иметь нулевые ссылки, лежащие вокруг. Так что сборщик мусора должен собрать оба объекта. Но он только собирает участников, оставляя Генератор без дела:
ruby-1.9.2-p180 :001 > Member.get(Generator.new)
Provided generator: #<Generator:0x007fcf398015c8>
=> #<Member:0x007fcf39801550>
ruby-1.9.2-p180 :006 > GC.start
=> nil
ruby-1.9.2-p180 :007 > ObjectSpace.each_object(Member){|m| puts m}
=> 0
ruby-1.9.2-p180 :008 > ObjectSpace.each_object(Generator){|g| puts g}
#<Generator:0x007fcf398015c8>
=> 1
(ObjectSpace.each_object
, насколько я понимаю, возвращает список ссылок на данный класс, все еще находящийся в куче рубина.)
Почему до сих пор остается ссылка на объект Generator, сидящий вокруг? Я никоим образом не сохранил его в переменной, поэтому больше не должно быть ссылок на него. Объект Member был собран, поэтому его переменная экземпляра, которая ссылается на класс Generator, не должна препятствовать его сбору.
Мне тоже не просто любопытно. У нас есть приложение Sinatra, которое имеет схожую структуру классов, а эквивалентный класс Generator хранит огромный кэш объектов Member, несколько сотен мегабайт на запрос, и его никогда не собирают. В Ruby не хватает памяти, и серверу приложений приходится перезапускать каждые дюжину или около того запросов.