def foo
str = 'hello, world' * 1_000_000
ObjectSpace.define_finalizer(str, proc { |id| puts "removing #{id}" })
end
100.times do |i|
puts i
foo
sleep 1
end
原本预想的结果是: 程序运行过程中 GC 会时不时的销毁对象, 从而使得终端打印出 ‘removing object 19199560’ 这样字符串出来, 并且该进程的内存占用应该是在一个小范围上下浮动.
实际发生的结果是: 程序运行过程中 GC 并没有销毁对象, 直到结束后终端连续打印出 ‘removing object 19199560’ 这样的字符串, 并且该进程的内存占用持续上涨.
那么是什么原因导致这样的结果?
经过查看 ObjectSpace.define_finalizer 的 实现:
static VALUE
define_final0(VALUE obj, VALUE block)
{
rb_objspace_t *objspace = &rb_objspace;
VALUE table;
st_data_t data;
RBASIC(obj)->flags |= FL_FINALIZE;
block = rb_ary_new3(2, INT2FIX(rb_safe_level()), block);
OBJ_FREEZE(block);
if (st_lookup(finalizer_table, obj, &data)) {
table = (VALUE)data;
rb_ary_push(table, block);
}
else {
table = rb_ary_new3(1, block);
RBASIC_CLEAR_CLASS(table);
st_add_direct(finalizer_table, obj, table);
}
return block;
}
可以知道, str 的 finalize proc 被存放在 Ruby 全局的 finalizer_table 中, 由于闭包特性 finalize proc 的上下文保存了 str 这个变量, 导致其一直被使用, 从而无法被 GC 回收.
知道了原因之后, 便可以从 2 个方面修改上述代码, 使其按照我预想(正确)的方式运行:
def foo
str = 'hello, world' * 1_000_000
ObjectSpace.define_finalizer(str, proc { |id| puts "removing #{id}" })
str = nil
end
100.times do |i|
puts i
foo
sleep 1
end
class Finalizer
def self.finalize
proc { |id| puts "removing #{id}" }
end
end
def foo
str = 'hello, world' * 1_000_000
ObjectSpace.define_finalizer(str, Finalizer.finalizer)
end
100.times do |i|
puts i
foo
sleep 1
end
一般来说第二种方式更合适, 标准库中 lib/tempfile.rb 的实现便是一个很好的示例.
转自:http://zhouguangming.me/2014/11/17/trouble-with-object_space-define_finalizer