#define_method
will create a closure, without careful use it could cause memory leak, as pointed out here.
OpenStruct
use #define_method
to create accessors dynamically, we suspect that will cause memory leak.
So I create two scripts to prove it, however the result shows the opposite: OpenStruct
doesn't leak memory.
Here I create OpenStruct
repeatedly with big_hash, a hash with 5000 key-value pairs and each key/value is a very long string. Run the script ruby open_struct_leak.rb
will print memory stats diff between each loop:
ruby open_struct_leak.rb
TOTAL 46613 T_HASH -3 T_STRING 16838 GC 32
TOTAL 31073 T_HASH 2 T_STRING 1 GC 22
TOTAL 2454 T_HASH 0 T_STRING 0 GC 22
TOTAL 2045 T_HASH 0 T_STRING 0 GC 22
TOTAL 1636 T_HASH 0 T_STRING 0 GC 22
TOTAL 1225 T_HASH 0 T_STRING 0 GC 22
TOTAL 1227 T_HASH 0 T_STRING 0 GC 22
TOTAL 818 T_HASH 0 T_STRING 0 GC 22
TOTAL 818 T_HASH 0 T_STRING 0 GC 22
TOTAL 818 T_HASH 0 T_STRING 0 GC 22
TOTAL 409 T_HASH 0 T_STRING 0 GC 22
TOTAL 409 T_HASH 0 T_STRING 0 GC 22
TOTAL 409 T_HASH 0 T_STRING 0 GC 22
TOTAL 409 T_HASH 0 T_STRING 0 GC 22
TOTAL 409 T_HASH 0 T_STRING 0 GC 22
TOTAL 409 T_HASH 0 T_STRING 0 GC 22
TOTAL 409 T_HASH 0 T_STRING 0 GC 22
TOTAL 409 T_HASH 0 T_STRING 0 GC 22
TOTAL 0 T_HASH 0 T_STRING 0 GC 21
TOTAL 0 T_HASH 0 T_STRING 0 GC 21
TOTAL 0 T_HASH 0 T_STRING 0 GC 21
TOTAL 0 T_HASH 0 T_STRING 0 GC 21
After serveral loop the diff of TOTAL/hash/string stablize at 0, thus no object leak. The hash used to initialize OpenStruct
is garbage collected correctly.
Comment lines below line A and uncomment code below line B to see a comparative result:
ruby open_struct_leak.rb
TOTAL 6538 T_HASH -2 T_STRING 11843 GC 10
TOTAL 10220 T_HASH 3 T_STRING 10001 GC 9
TOTAL 9811 T_HASH 1 T_STRING 10000 GC 9
TOTAL 10219 T_HASH 1 T_STRING 10000 GC 9
TOTAL 9809 T_HASH 1 T_STRING 10000 GC 9
TOTAL 10224 T_HASH 1 T_STRING 10000 GC 9
TOTAL 9809 T_HASH 1 T_STRING 10000 GC 9
TOTAL 10221 T_HASH 1 T_STRING 10000 GC 9
TOTAL 9814 T_HASH 1 T_STRING 10000 GC 9
TOTAL 10218 T_HASH 1 T_STRING 10000 GC 9
TOTAL 9814 T_HASH 1 T_STRING 10000 GC 9
TOTAL 10224 T_HASH 1 T_STRING 10000 GC 9
TOTAL 9816 T_HASH 1 T_STRING 10000 GC 9
TOTAL 9810 T_HASH 1 T_STRING 10000 GC 9
TOTAL 10216 T_HASH 1 T_STRING 10000 GC 9
There're 1 hash and 10000 strings created in each loop, because there's always reference on the hash created, the hash cannot be GCed.
Why doesn't #define_method
in OpenStruct
leak memory? Check its source code:
def new_ostruct_member(name)
name = name.to_sym
unless self.respond_to?(name)
class << self; self; end.class_eval do
define_method(name) { @table[name] }
define_method("#{name}=") { |x| modifiable[name] = x }
end
end
name
end
#define_method
is called on OpenStruct
object's singleton class. Once the object is GCed, its singleton class and singleton methods is also GCed too. We can validate this with another script.
The script includes two test subjects:
DefineMethodOnSingleton
will define a new method on every object's singleton class when initialize.
DefineMethodOnBase
will define a new method on base class when initialize.
Results:
DefineMethodOnSingleton (no leakage, the one T_STRING created every loop is the method name)
TOTAL 17168 T_HASH 3 T_STRING 9787 GC 9
TOTAL 9402 T_HASH 1 T_STRING 2 GC 9
TOTAL 818 T_HASH 1 T_STRING 1 GC 9
TOTAL 409 T_HASH 0 T_STRING 1 GC 9
TOTAL 409 T_HASH 0 T_STRING 1 GC 9
TOTAL 409 T_HASH 0 T_STRING 1 GC 9
TOTAL 409 T_HASH 0 T_STRING 1 GC 9
TOTAL 0 T_HASH 0 T_STRING 1 GC 9
TOTAL 0 T_HASH 0 T_STRING 1 GC 9
TOTAL 0 T_HASH 0 T_STRING 1 GC 9
TOTAL 0 T_HASH 0 T_STRING 1 GC 9
TOTAL 0 T_HASH 0 T_STRING 1 GC 9
TOTAL 0 T_HASH 0 T_STRING 1 GC 9
DefineMethodOnBase (leaking with a big hash every loop)
TOTAL 17169 T_HASH 3 T_STRING 9787 GC 9
TOTAL 9813 T_HASH 2 T_STRING 10002 GC 9
TOTAL 9812 T_HASH 2 T_STRING 10001 GC 9
TOTAL 10219 T_HASH 1 T_STRING 10001 GC 9
TOTAL 9810 T_HASH 1 T_STRING 10001 GC 9
TOTAL 10222 T_HASH 1 T_STRING 10001 GC 9
TOTAL 9810 T_HASH 1 T_STRING 10001 GC 9
TOTAL 9809 T_HASH 1 T_STRING 10001 GC 9
TOTAL 10224 T_HASH 1 T_STRING 10001 GC 9
TOTAL 9813 T_HASH 1 T_STRING 10001 GC 9
TOTAL 10216 T_HASH 1 T_STRING 10001 GC 9
TOTAL 9811 T_HASH 1 T_STRING 10001 GC 9
TOTAL 10216 T_HASH 1 T_STRING 10001 GC 9
TOTAL 9814 T_HASH 1 T_STRING 10001 GC 9
TOTAL 10218 T_HASH 1 T_STRING 10001 GC 9
TOTAL 9805 T_HASH 1 T_STRING 10001 GC 9
And updated link to tenderlove's post: http://tenderlovemaking.com/2013/03/03/dynamic_method_definitions.html