Question

I'd like to know why the following code apparently doesn't garbage collect anonymous modules that are supposedly not referenced anywhere anymore (not extended/included, not named, containing array set to nil).

I'd appreciate if anyone could clarify what's going on under the hood with relatively simple/general programming words. Is there a special ruby way to achieve this ? Can't anonymous modules/classes be garbage collected no matter what ? Or was i simply mislead by the memory stats i got ?

NOTE : i'm using ruby 1.9.3 ; don't know if ruby 2.x would change anything at all... NOTE 2 : the result is the same whether or not the module defines the foo method

Thanks in advance.

puts("INITIAL OBJECT SPACE OBJECTS : #{ObjectSpace.count_objects}")


i = 100000
ms = []

i.times do
    ms << Module.new do
        def foo()
             puts('foo method called')
        end
    end
end

puts("#{i} MODULES CREATED")
puts("OBJECT SPACE OBJECTS : #{ObjectSpace.count_objects}")

ms = nil
ObjectSpace.garbage_collect

puts("#{i} MODULES GARBAGE COLLECTED")
puts("WAITING TO END PROGRAM")

stop = gets
puts("FINAL OBJECT SPACE OBJECTS : #{ObjectSpace.count_objects}")

I say "apparently doesn't garbage collect" because my OS task manager doesn't show any reduction in memory usage from the process, and calling ObjectSpace.count_objects yields the following, which i read (wrongly so ?) as : no the memory used by your modules has not been freed.

INITIAL OBJECT SPACE OBJECTS : {:TOTAL=>14730, :FREE=>251, :T_OBJECT=>8, :T_CLASS=>542, :T_MODULE=>21, :T_FLOAT=>7, :T_STRING=>6349, :T_REGEXP=>24, :T_ARRAY=>1009, :T_HASH=>14, :T_BIGNUM=>3, :T_FILE=>10, :T_DATA=>408, :T_MATCH=>108, :T_COMPLEX=>1, :T_NODE=>5956, :T_ICLASS=>19}

100000 MODULES CREATED

OBJECT SPACE OBJECTS : {:TOTAL=>311794, :FREE=>59829, :T_OBJECT=>6, :T_CLASS=>542, :T_MODULE=>100021, :T_FLOAT=>7, :T_STRING=>3332, :T_REGEXP=>22, :T_ARRAY=>23868, :T_HASH=>10, :T_BIGNUM=>3, :T_FILE=>3, :T_DATA=>100324, :T_COMPLEX=>1, :T_NODE=>23807, :T_ICLASS=>19}

100000 MODULES GARBAGE COLLECTED WAITING TO END PROGRAM

FINAL OBJECT SPACE OBJECTS : {:TOTAL=>311794, :FREE=>107155, :T_OBJECT=>6, :T_CLASS=>542, :T_MODULE=>100021, :T_FLOAT=>7, :T_STRING=>3335, :T_REGEXP=>22, :T_ARRAY=>203, :T_HASH=>10, :T_BIGNUM=>3, :T_FILE=>3, :T_DATA=>100324, :T_COMPLEX=>1, :T_NODE=>143, :T_ICLASS=>19}

Was it helpful?

Solution

Calling GC.start or ObjectSpace.garbage_collect does not mean that a garbage collection will be performed. It's only a hint to the Ruby runtime.

On some platforms it isn't even possible for the Ruby runtime to initiate a garbage collection, because the garbage collector isn't even part of the Ruby runtime, e.g on JRuby or IronRuby.

In general, the Ruby runtime will decide on its own when it will perform a garbage collection. Usually, that's when it runs out of memory. 100000 modules aren't that big, so there's simply no need to perform a GC cycle.

Also, most Ruby implementations will never release memory back to the OS, even after a GC cycle.

So, just the fact that the ObjectSpace doesn't shrink doesn't necessarily mean that you have memory leak. It could simply mean that the Ruby runtime didn't yet deem it necessary to run a GC cycle.

Also note that if you run this code from IRb, Pry, an IDE console or some other non-standard environment, then it may be the case that those hold on to some of those modules. For example, Pry stores the results of the last 100 commands in a history array. (Try evaluating _out_[5] in Pry, after entering your example program.)

OTHER TIPS

You can Look into the method GC#start

puts "INITIAL OBJECT SPACE OBJECTS : #{ObjectSpace.count_objects[:T_MODULE]}"
i = 10
ms = []

i.times do
    ms << Module.new do
        def foo()
             puts('foo method called')
        end
    end
end

puts "#{i} MODULES CREATED" 
puts "OBJECT SPACE OBJECTS : #{ObjectSpace.count_objects[:T_MODULE]}"


p ms
ms = nil
GC.start

puts "#{i} MODULES GARBAGE COLLECTED"
puts "WAITING TO END PROGRAM"
p ms 
puts "FINAL OBJECT SPACE OBJECTS : #{ObjectSpace.count_objects[:T_MODULE]}"

Output:

INITIAL OBJECT SPACE OBJECTS : 21
10 MODULES CREATED
OBJECT SPACE OBJECTS : 31
[#<Module:0xe33a48>, #<Module:0xe338c8>, #<Module:0xe33808>, #<Module:0xe337a8>, #<Module:0xe33610>, 
#<Module:0xe334c0>, #<Module:0xe33328>, #<Module:0xe33250>, #<Module:0xe33118>, #<Module:0xe33088>]
10 MODULES GARBAGE COLLECTED
WAITING TO END PROGRAM
nil
FINAL OBJECT SPACE OBJECTS : 23

So you can see after garbage collection I got the final count as 23 instead of 21,which is exactly explained by @Jörg W Mittag.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top