For the curious: It turns out my memory leak had nothing to do with what I put in the sample here. I thought I had the issue nailed down to some sample code, but my sample code had different issues. I did end up finding my real issue though, and that’s here: Ruby Symbol#to_proc leaks references in 1.9.2-p180?
I have two ruby classes (Generator and Member, in this example) where Generator serves as a factory (in a loose definition of the term) of Member objects, and each Member holds a reference to the Generator that constructed it.
Code:
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
Using IRB, I’d expect that if I simply call I simply call Member.get(Generator.new), but not actually assign the result to anything, both the reference to the newly-constructed Generator object and the newly-constructed Member object should have zero references laying around. So the garbage collector should collect both objects. But it only collects the Members, leaving the Generator sitting around:
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, as I understand it, returns a list of references to a given class still on ruby’s heap.)
Why is there still a reference to the Generator object sitting around? I haven’t saved it to a variable in any way, so there shouldn’t be anything referencing it anymore. The Member object got collected, so its instance variable that references the Generator class shouldn’t be preventing it from getting collected.
I’m not just curious, either. We have a Sinatra app that has a similar class structure, and the equivalent Generator class stores a huge cache of Member objects, several hundred megs per request, and it never gets collected. Ruby runs out of memory and the app server has to restart every dozen or so requests.
When you call
you set the class instance variable
@generatorfor the Member class:And by calling the initial line Member.get(Generator.new), there is nothing that would create a Member, you simply create an instance of Generator, which is then assigned to the class instance variable.
That leaves:
-> The results you receive are perfectly normal, nothing wrong with Ruby’s Garbage Collection.