模块 ObjectSpace

ObjectSpace 模块包含一些与垃圾收集机制交互的例程,并允许您使用迭代器遍历所有存活对象。

ObjectSpace 还提供对对象终结器的支持,终结器是当特定对象即将被垃圾收集销毁时将被调用的 proc。有关如何正确使用此方法的重要信息,请参阅 ObjectSpace.define_finalizer 的文档。

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

产生

Finalizer two on 537763470
Finalizer one on 537763480

公共类方法

count_objects([result_hash]) → hash 点击切换源代码

统计所有按类型分组的对象。

它返回一个哈希,例如

{
  :TOTAL=>10000,
  :FREE=>3011,
  :T_OBJECT=>6,
  :T_CLASS=>404,
  # ...
}

返回的哈希内容是实现特定的。它可能会在将来发生变化。

:T_ 开头的键表示存活对象。例如,:T_ARRAY 是数组的数量。:FREE 表示当前未使用的对象槽。:TOTAL 表示上述的总和。

如果给出了可选参数 result_hash,它将被覆盖并返回。这旨在避免探测效应。

h = {}
ObjectSpace.count_objects(h)
puts h
# => { :TOTAL=>10000, :T_CLASS=>158280, :T_MODULE=>20672, :T_STRING=>527249 }

此方法仅预期在 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];
        short stride = page->slot_size;

        uintptr_t p = (uintptr_t)page->start;
        uintptr_t pend = p + page->total_slots * stride;
        for (;p < pend; p += stride) {
            VALUE vp = (VALUE)p;
            GC_ASSERT((NUM_IN_PAGE(vp) * BASE_SLOT_SIZE) % page->slot_size == 0);

            void *poisoned = asan_unpoison_object_temporary(vp);
            if (RANY(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 (NIL_P(hash)) {
        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;
}
define_finalizer(obj, aProc=proc()) 点击切换源代码

aProc 添加为终结器,在 obj 被销毁后调用。obj 的对象 ID 将作为参数传递给 aProc。如果 aProc 是一个 lambda 或方法,请确保它可以接受一个参数调用。

返回值是一个数组 [0, aProc]

两种推荐的模式是在非实例方法中创建终结器 proc,在该方法中可以安全地捕获所需的状态,或者使用自定义可调用对象,该对象将所需的状态显式存储为实例变量。

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

请注意,如果您的终结器引用了要终结的对象,它将永远不会在 GC 上运行,尽管它仍然会在退出时运行。如果您将要终结的对象捕获为终结器的接收者,您将收到警告。

class CapturesSelf
  def initialize(name)
    ObjectSpace.define_finalizer(self, proc {
      # this finalizer will only be run on exit
      puts "finalizing #{name}"
    })
  end
end

还要注意,终结可能是不可预测的,并且除了在退出时之外,永远不能保证运行。

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 rb_define_finalizer_no_check(obj, block);
}
each_object([module]) {|obj| ... } → integer 点击切换源代码
each_object([module]) → an_enumerator

在当前 Ruby 进程中,对每个存活的非立即对象调用一次块。如果指定了module,则仅对与module匹配(或为其子类)的类或模块调用块。返回找到的对象数量。立即对象(FixnumSymboltruefalsenil)永远不会返回。在下面的示例中,each_object 返回我们定义的数字和在 Math 模块中定义的几个常量。

如果没有给出块,则返回一个枚举器。

a = 102.7
b = 95       # Won't be returned
c = 12345678987654321
count = ObjectSpace.each_object(Numeric) {|x| p x }
puts "Total count: #{count}"

产生

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);
}
garbage_collect(full_mark: true, immediate_mark: true, immediate_sweep: true) 点击切换源代码

GC.start 的别名

# File ruby_3_3_0/gc.rb, line 327
def garbage_collect full_mark: true, immediate_mark: true, immediate_sweep: true
  Primitive.gc_start_internal full_mark, immediate_mark, immediate_sweep, false
end
undefine_finalizer(obj) 点击切换源代码

删除obj的所有终结器。

static VALUE
undefine_final(VALUE os, VALUE obj)
{
    return rb_undefine_finalizer(obj);
}

私有实例方法

garbage_collect(full_mark: true, immediate_mark: true, immediate_sweep: true) 点击切换源代码

GC.start 的别名

# File ruby_3_3_0/gc.rb, line 327
def garbage_collect full_mark: true, immediate_mark: true, immediate_sweep: true
  Primitive.gc_start_internal full_mark, immediate_mark, immediate_sweep, false
end