The ObjectSpace module contains a number of routines that interact with the garbage collection facility and allow you to traverse all living objects with an iterator.
ObjectSpace also provides support for object
finalizers, procs that will be called when a specific object is about to be
destroyed by garbage collection. See the documentation for
ObjectSpace.define_finalizer
for important information on how
to use this method correctly.
a = "A" b = "B" ObjectSpace.define_finalizer(a, proc {|id| puts "Finalizer one on #{id}" }) ObjectSpace.define_finalizer(b, proc {|id| puts "Finalizer two on #{id}" }) a = nil b = nil
produces:
Finalizer two on 537763470 Finalizer one on 537763480
static VALUE os_id2ref(VALUE os, VALUE objid) { return id2ref(objid); }
Counts all objects grouped by type.
It returns a hash, such as:
{ :TOTAL=>10000, :FREE=>3011, :T_OBJECT=>6, :T_CLASS=>404, # ... }
The contents of the returned hash are implementation specific. It may be changed in future.
The keys starting with :T_
means live objects. For example,
:T_ARRAY
is the number of arrays. :FREE
means
object slots which is not used now. :TOTAL
means sum of above.
If the optional argument result_hash
is given, it is
overwritten and returned. This is intended to avoid probe effect.
h = {} ObjectSpace.count_objects(h) puts h # => { :TOTAL=>10000, :T_CLASS=>158280, :T_MODULE=>20672, :T_STRING=>527249 }
This method is only expected to work on C Ruby.
static VALUE count_objects(int argc, VALUE *argv, VALUE os) { rb_objspace_t *objspace = &rb_objspace; size_t counts[T_MASK+1]; size_t freed = 0; size_t total = 0; size_t i; VALUE hash = Qnil; if (rb_check_arity(argc, 0, 1) == 1) { hash = argv[0]; if (!RB_TYPE_P(hash, T_HASH)) rb_raise(rb_eTypeError, "non-hash given"); } for (i = 0; i <= T_MASK; i++) { counts[i] = 0; } for (i = 0; i < heap_allocated_pages; i++) { struct heap_page *page = heap_pages_sorted[i]; RVALUE *p, *pend; p = page->start; pend = p + page->total_slots; for (;p < pend; p++) { VALUE vp = (VALUE)p; void *poisoned = asan_poisoned_object_p(vp); asan_unpoison_object(vp, false); if (p->as.basic.flags) { counts[BUILTIN_TYPE(vp)]++; } else { freed++; } if (poisoned) { GC_ASSERT(BUILTIN_TYPE(vp) == T_NONE); asan_poison_object(vp); } } total += page->total_slots; } if (hash == Qnil) { hash = rb_hash_new(); } else if (!RHASH_EMPTY_P(hash)) { rb_hash_stlike_foreach(hash, set_zero, hash); } rb_hash_aset(hash, ID2SYM(rb_intern("TOTAL")), SIZET2NUM(total)); rb_hash_aset(hash, ID2SYM(rb_intern("FREE")), SIZET2NUM(freed)); for (i = 0; i <= T_MASK; i++) { VALUE type = type_sym(i); if (counts[i]) rb_hash_aset(hash, type, SIZET2NUM(counts[i])); } return hash; }
Adds aProc as a finalizer, to be called after obj was destroyed. The object ID of the obj will be passed as an argument to aProc. If aProc is a lambda or method, make sure it can be called with a single argument.
The return value is an array [0, aProc]
.
The two recommended patterns are to either create the finaliser proc in a non-instance method where it can safely capture the needed state, or to use a custom callable object that stores the needed state explicitly as instance variables.
class Foo def initialize(data_needed_for_finalization) ObjectSpace.define_finalizer(self, self.class.create_finalizer(data_needed_for_finalization)) end def self.create_finalizer(data_needed_for_finalization) proc { puts "finalizing #{data_needed_for_finalization}" } end end class Bar class Remover def initialize(data_needed_for_finalization) @data_needed_for_finalization = data_needed_for_finalization end def call(id) puts "finalizing #{@data_needed_for_finalization}" end end def initialize(data_needed_for_finalization) ObjectSpace.define_finalizer(self, Remover.new(data_needed_for_finalization)) end end
Note that if your finalizer references the object to be finalized it will never be run on GC, although it will still be run at exit. You will get a warning if you capture the object to be finalized as the receiver of the finalizer.
class CapturesSelf def initialize(name) ObjectSpace.define_finalizer(self, proc { # this finalizer will only be run on exit puts "finalizing #{name}" }) end end
Also note that finalization can be unpredictable and is never guaranteed to be run except on exit.
static VALUE define_final(int argc, VALUE *argv, VALUE os) { VALUE obj, block; rb_scan_args(argc, argv, "11", &obj, &block); should_be_finalizable(obj); if (argc == 1) { block = rb_block_proc(); } else { should_be_callable(block); } if (rb_callable_receiver(block) == obj) { rb_warn("finalizer references object to be finalized"); } return define_final0(obj, block); }
Calls the block once for each living, nonimmediate object in this Ruby
process. If module is specified, calls the block for only those
classes or modules that match (or are a subclass of) module.
Returns the number of objects found. Immediate objects
(Fixnum
s, Symbol
s true
,
false
, and nil
) are never returned. In the
example below, each_object returns both the numbers we defined and several
constants defined in the Math module.
If no block is given, an enumerator is returned instead.
a = 102.7 b = 95 # Won't be returned c = 12345678987654321 count = ObjectSpace.each_object(Numeric) {|x| p x } puts "Total count: #{count}"
produces:
12345678987654321 102.7 2.71828182845905 3.14159265358979 2.22044604925031e-16 1.7976931348623157e+308 2.2250738585072e-308 Total count: 7
static VALUE os_each_obj(int argc, VALUE *argv, VALUE os) { VALUE of; of = (!rb_check_arity(argc, 0, 1) ? 0 : argv[0]); RETURN_ENUMERATOR(os, 1, &of); return os_obj_of(of); }