模块 ObjectSpace

objspace 库扩展了 ObjectSpace 模块,并添加了一些方法来获取有关对象/内存管理的内部统计信息。

您需要 require 'objspace' 来使用此扩展模块。

通常,如果您不了解 MRI 实现,则 **不应** 使用此库。主要的是,此库是为(内存)分析器开发人员和需要了解 MRI 内存使用情况的 MRI 开发人员准备的。

公共类方法

allocation_class_path(object) → string 点击以切换源代码

返回给定 object 的类。

class A
  def foo
    ObjectSpace::trace_object_allocations do
      obj = Object.new
      p "#{ObjectSpace::allocation_class_path(obj)}"
    end
  end
end

A.new.foo #=> "Class"

有关更多信息和示例,请参见 ::trace_object_allocations

static VALUE
allocation_class_path(VALUE self, VALUE obj)
{
    struct allocation_info *info = lookup_allocation_info(obj);

    if (info && info->class_path) {
        return rb_str_new2(info->class_path);
    }
    else {
        return Qnil;
    }
}
allocation_generation(object) → integer or nil 点击以切换源代码

返回给定 object 的垃圾收集器代。

class B
  include ObjectSpace

  def foo
    trace_object_allocations do
      obj = Object.new
      p "Generation is #{allocation_generation(obj)}"
    end
  end
end

B.new.foo #=> "Generation is 3"

有关更多信息和示例,请参见 ::trace_object_allocations

static VALUE
allocation_generation(VALUE self, VALUE obj)
{
    struct allocation_info *info = lookup_allocation_info(obj);
    if (info) {
        return SIZET2NUM(info->generation);
    }
    else {
        return Qnil;
    }
}
allocation_method_id(object) → string 点击以切换源代码

返回给定 object 的方法标识符。

class A
  include ObjectSpace

  def foo
    trace_object_allocations do
      obj = Object.new
      p "#{allocation_class_path(obj)}##{allocation_method_id(obj)}"
    end
  end
end

A.new.foo #=> "Class#new"

有关更多信息和示例,请参见 ::trace_object_allocations

static VALUE
allocation_method_id(VALUE self, VALUE obj)
{
    struct allocation_info *info = lookup_allocation_info(obj);
    if (info) {
        return info->mid;
    }
    else {
        return Qnil;
    }
}
allocation_sourcefile(object) → string 点击以切换源代码

返回给定 object 的源文件来源。

有关更多信息和示例,请参见 ::trace_object_allocations

static VALUE
allocation_sourcefile(VALUE self, VALUE obj)
{
    struct allocation_info *info = lookup_allocation_info(obj);

    if (info && info->path) {
        return rb_str_new2(info->path);
    }
    else {
        return Qnil;
    }
}
allocation_sourceline(object) → integer 点击以切换源代码

返回给定 object 的源代码的原始行。

有关更多信息和示例,请参见 ::trace_object_allocations

static VALUE
allocation_sourceline(VALUE self, VALUE obj)
{
    struct allocation_info *info = lookup_allocation_info(obj);

    if (info) {
        return INT2FIX(info->line);
    }
    else {
        return Qnil;
    }
}
count_imemo_objects([result_hash]) → hash 点击以切换源代码

计算每个 T_IMEMO 类型的对象。

此方法仅供对 Ruby 程序的性能和内存使用情况感兴趣的 MRI 开发人员使用。

它返回一个哈希,如下所示:

{:imemo_ifunc=>8,
 :imemo_svar=>7,
 :imemo_cref=>509,
 :imemo_memo=>1,
 :imemo_throw_data=>1}

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

返回的哈希的内容是实现特定的,并且将来可能会改变。

在此版本中,键是符号对象。

此方法仅适用于 C Ruby。

static VALUE
count_imemo_objects(int argc, VALUE *argv, VALUE self)
{
    VALUE hash = setup_hash(argc, argv);

    if (imemo_type_ids[0] == 0) {
#define INIT_IMEMO_TYPE_ID(n) (imemo_type_ids[n] = rb_intern_const(#n))
        INIT_IMEMO_TYPE_ID(imemo_env);
        INIT_IMEMO_TYPE_ID(imemo_cref);
        INIT_IMEMO_TYPE_ID(imemo_svar);
        INIT_IMEMO_TYPE_ID(imemo_throw_data);
        INIT_IMEMO_TYPE_ID(imemo_ifunc);
        INIT_IMEMO_TYPE_ID(imemo_memo);
        INIT_IMEMO_TYPE_ID(imemo_ment);
        INIT_IMEMO_TYPE_ID(imemo_iseq);
        INIT_IMEMO_TYPE_ID(imemo_tmpbuf);
        INIT_IMEMO_TYPE_ID(imemo_ast);
        INIT_IMEMO_TYPE_ID(imemo_parser_strterm);
        INIT_IMEMO_TYPE_ID(imemo_callinfo);
        INIT_IMEMO_TYPE_ID(imemo_callcache);
        INIT_IMEMO_TYPE_ID(imemo_constcache);
#undef INIT_IMEMO_TYPE_ID
    }

    each_object_with_flags(count_imemo_objects_i, (void *)hash);

    return hash;
}
count_nodes([result_hash]) → hash 点击切换源代码

统计每个节点类型的节点数量。

此方法仅供对 Ruby 程序的性能和内存使用情况感兴趣的 MRI 开发人员使用。

它返回一个哈希,如下所示:

{:NODE_METHOD=>2027, :NODE_FBODY=>1927, :NODE_CFUNC=>1798, ...}

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

注意:返回的哈希表内容由实现定义。它可能在将来发生变化。

此方法仅适用于 C Ruby。

static VALUE
count_nodes(int argc, VALUE *argv, VALUE os)
{
    return setup_hash(argc, argv);
}
count_objects_size([result_hash]) → hash 点击切换源代码

统计每个类型的对象大小(以字节为单位)。

请注意,此信息不完整。您需要将此信息视为仅为提示。特别是,T_DATA 的总大小可能不正确。

它返回一个哈希,如下所示:

{:TOTAL=>1461154, :T_CLASS=>158280, :T_MODULE=>20672, :T_STRING=>527249, ...}

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

返回的哈希表内容由实现定义。它可能在将来发生变化。

此方法仅适用于 C Ruby。

static VALUE
count_objects_size(int argc, VALUE *argv, VALUE os)
{
    size_t counts[T_MASK+1];
    size_t total = 0;
    enum ruby_value_type i;
    VALUE hash = setup_hash(argc, argv);

    for (i = 0; i <= T_MASK; i++) {
        counts[i] = 0;
    }

    each_object_with_flags(cos_i, &counts[0]);

    for (i = 0; i <= T_MASK; i++) {
        if (counts[i]) {
            VALUE type = type2sym(i);
            total += counts[i];
            rb_hash_aset(hash, type, SIZET2NUM(counts[i]));
        }
    }
    rb_hash_aset(hash, ID2SYM(rb_intern("TOTAL")), SIZET2NUM(total));
    return hash;
}
count_symbols([result_hash]) → hash 点击切换源代码

统计每个 Symbol 类型的符号数量。

此方法仅供对 Ruby 程序的性能和内存使用情况感兴趣的 MRI 开发人员使用。

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

注意:返回的哈希表内容由实现定义。它可能在将来发生变化。

此方法仅适用于 C Ruby。

在这个版本的 MRI 中,它们有 3 种类型的 Symbol(和 1 个总计)。

* mortal_dynamic_symbol: GC target symbols (collected by GC)
* immortal_dynamic_symbol: Immortal symbols promoted from dynamic symbols (do not collected by GC)
* immortal_static_symbol: Immortal symbols (do not collected by GC)
* immortal_symbol: total immortal symbols (immortal_dynamic_symbol+immortal_static_symbol)
static VALUE
count_symbols(int argc, VALUE *argv, VALUE os)
{
    struct dynamic_symbol_counts dynamic_counts = {0, 0};
    VALUE hash = setup_hash(argc, argv);

    size_t immortal_symbols = rb_sym_immortal_count();
    each_object_with_flags(cs_i, &dynamic_counts);

    rb_hash_aset(hash, ID2SYM(rb_intern("mortal_dynamic_symbol")),   SIZET2NUM(dynamic_counts.mortal));
    rb_hash_aset(hash, ID2SYM(rb_intern("immortal_dynamic_symbol")), SIZET2NUM(dynamic_counts.immortal));
    rb_hash_aset(hash, ID2SYM(rb_intern("immortal_static_symbol")),  SIZET2NUM(immortal_symbols - dynamic_counts.immortal));
    rb_hash_aset(hash, ID2SYM(rb_intern("immortal_symbol")),         SIZET2NUM(immortal_symbols));

    return hash;
}
count_tdata_objects([result_hash]) → hash 点击切换源代码

统计每个 T_DATA 类型的对象数量。

此方法仅供对 Ruby 程序的性能和内存使用情况感兴趣的 MRI 开发人员使用。

它返回一个哈希,如下所示:

{RubyVM::InstructionSequence=>504, :parser=>5, :barrier=>6,
 :mutex=>6, Proc=>60, RubyVM::Env=>57, Mutex=>1, Encoding=>99,
 ThreadGroup=>1, Binding=>1, Thread=>1, RubyVM=>1, :iseq=>1,
 Random=>1, ARGF.class=>1, Data=>1, :autoload=>3, Time=>2}
# T_DATA objects existing at startup on r32276.

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

返回的哈希的内容是实现特定的,并且将来可能会改变。

在这个版本中,键是 Class 对象或 Symbol 对象。

如果对象是普通(可访问)对象,则键是 Class 对象。如果对象不是普通(内部)对象,则键是通过 rb_data_type_struct 注册的符号名称。

此方法仅适用于 C Ruby。

static VALUE
count_tdata_objects(int argc, VALUE *argv, VALUE self)
{
    VALUE hash = setup_hash(argc, argv);
    each_object_with_flags(cto_i, (void *)hash);
    return hash;
}
internal_class_of(obj) → Class or Module 点击切换源代码
MRI 特定功能

返回 obj 的内部类。

obj 可以是 InternalObjectWrapper 的实例。

请注意,您不应该在应用程序中使用此方法。

static VALUE
objspace_internal_class_of(VALUE self, VALUE obj)
{
    VALUE klass;

    if (rb_typeddata_is_kind_of(obj, &iow_data_type)) {
        obj = (VALUE)DATA_PTR(obj);
    }

    if (RB_TYPE_P(obj, T_IMEMO)) {
        return Qnil;
    }
    else {
        klass = CLASS_OF(obj);
        return wrap_klass_iow(klass);
    }
}
internal_super_of(cls) → Class or Module 点击切换源代码
MRI 特定功能

返回 cls(Class 或 Module)的内部超类。

obj 可以是 InternalObjectWrapper 的实例。

请注意,您不应该在应用程序中使用此方法。

static VALUE
objspace_internal_super_of(VALUE self, VALUE obj)
{
    VALUE super;

    if (rb_typeddata_is_kind_of(obj, &iow_data_type)) {
        obj = (VALUE)DATA_PTR(obj);
    }

    switch (OBJ_BUILTIN_TYPE(obj)) {
      case T_MODULE:
      case T_CLASS:
      case T_ICLASS:
        super = RCLASS_SUPER(obj);
        break;
      default:
        rb_raise(rb_eArgError, "class or module is expected");
    }

    return wrap_klass_iow(super);
}
memsize_of(obj) → Integer 点击切换源代码

返回 obj 的内存占用大小(以字节为单位)。

请注意,返回的大小不完整。您需要将此信息视为仅为提示。特别是,T_DATA 的大小可能不正确。

此方法仅适用于 C Ruby。

从 Ruby 2.2 开始,memsize_of(obj) 返回的内存大小包括 sizeof(RVALUE)。

static VALUE
memsize_of_m(VALUE self, VALUE obj)
{
    return SIZET2NUM(rb_obj_memsize_of(obj));
}
memsize_of_all([klass]) → Integer 点击切换源代码

返回所有存活对象的内存占用大小(以字节为单位)。

如果给定 klass(应该是 Class 对象),则返回给定类实例的总内存大小。

请注意,返回的大小是不完整的。您需要将此信息视为仅为 **提示**。特别是,T_DATA 的大小可能不正确。

请注意,此方法 **不** 返回总的 malloc 内存大小。

此方法可以通过以下 Ruby 代码定义

def memsize_of_all klass = false
  total = 0
  ObjectSpace.each_object{|e|
    total += ObjectSpace.memsize_of(e) if klass == false || e.kind_of?(klass)
  }
  total
end

此方法仅适用于 C Ruby。

static VALUE
memsize_of_all_m(int argc, VALUE *argv, VALUE self)
{
    struct total_data data = {0, 0};

    if (argc > 0) {
        rb_scan_args(argc, argv, "01", &data.klass);
    }

    each_object_with_flags(total_i, &data);
    return SIZET2NUM(data.total);
}
reachable_objects_from(obj) → array or nil click to toggle source
MRI 特定功能

返回从 'obj' 可达的所有对象。

此方法返回从 'obj' 可达的所有对象。

如果 'obj' 对同一个对象 'x' 有两个或多个引用,则返回的数组只包含一个 'x' 对象。

如果 'obj' 是不可标记(非堆管理)对象,例如 true、false、nil、符号和 Fixnums(以及 Flonum),则它只返回 nil。

如果 'obj' 对内部对象有引用,则它返回 ObjectSpace::InternalObjectWrapper 类的实例。此对象包含对内部对象的引用,您可以使用 'type' 方法检查内部对象的类型。

如果 'obj' 是 ObjectSpace::InternalObjectWrapper 类的实例,则此方法返回从 'obj' 指向的内部对象可达的所有对象。

使用此方法,您可以找到内存泄漏。

此方法仅预期在除 C Ruby 之外的环境中工作。

示例

ObjectSpace.reachable_objects_from(['a', 'b', 'c'])
#=> [Array, 'a', 'b', 'c']

ObjectSpace.reachable_objects_from(['a', 'a', 'a'])
#=> [Array, 'a', 'a', 'a'] # all 'a' strings have different object id

ObjectSpace.reachable_objects_from([v = 'a', v, v])
#=> [Array, 'a']

ObjectSpace.reachable_objects_from(1)
#=> nil # 1 is not markable (heap managed) object
static VALUE
reachable_objects_from(VALUE self, VALUE obj)
{
    if (rb_objspace_markable_object_p(obj)) {
        struct rof_data data;

        if (rb_typeddata_is_kind_of(obj, &iow_data_type)) {
            obj = (VALUE)DATA_PTR(obj);
        }

        data.refs = rb_obj_hide(rb_ident_hash_new());
        data.values = rb_ary_new();

        rb_objspace_reachable_objects_from(obj, reachable_object_from_i, &data);

        return data.values;
    }
    else {
        return Qnil;
    }
}
reachable_objects_from_root → hash click to toggle source
MRI 特定功能

返回从根目录可达的所有对象。

static VALUE
reachable_objects_from_root(VALUE self)
{
    struct rofr_data data;
    VALUE hash = data.categories = rb_ident_hash_new();
    data.last_category = 0;

    rb_objspace_reachable_objects_from_root(reachable_object_from_root_i, &data);
    rb_hash_foreach(hash, collect_values_of_values, hash);

    return hash;
}
trace_object_allocations { block } click to toggle source

ObjectSpace 扩展模块开始跟踪对象分配。

例如

require 'objspace'

class C
  include ObjectSpace

  def foo
    trace_object_allocations do
      obj = Object.new
      p "#{allocation_sourcefile(obj)}:#{allocation_sourceline(obj)}"
    end
  end
end

C.new.foo #=> "objtrace.rb:8"

此示例包含了 ObjectSpace 模块,以便于阅读,但您也可以使用 ::trace_object_allocations 符号(推荐)。

请注意,此功能会导致巨大的性能下降和巨大的内存消耗。

static VALUE
trace_object_allocations(VALUE self)
{
    trace_object_allocations_start(self);
    return rb_ensure(rb_yield, Qnil, trace_object_allocations_stop, self);
}
trace_object_allocations_clear click to toggle source

清除记录的跟踪信息。

static VALUE
trace_object_allocations_clear(VALUE self)
{
    struct traceobj_arg *arg = get_traceobj_arg();

    /* clear tables */
    st_foreach(arg->object_table, free_values_i, 0);
    st_clear(arg->object_table);
    st_foreach(arg->str_table, free_keys_i, 0);
    st_clear(arg->str_table);

    /* do not touch TracePoints */

    return Qnil;
}
trace_object_allocations_debug_start() click to toggle source
static VALUE
trace_object_allocations_debug_start(VALUE self)
{
    tmp_keep_remains = 1;
    if (object_allocations_reporter_registered == 0) {
        object_allocations_reporter_registered = 1;
        rb_bug_reporter_add(object_allocations_reporter, 0);
    }

    return trace_object_allocations_start(self);
}
trace_object_allocations_start click to toggle source

开始跟踪对象分配。

static VALUE
trace_object_allocations_start(VALUE self)
{
    struct traceobj_arg *arg = get_traceobj_arg();

    if (arg->running++ > 0) {
        /* do nothing */
    }
    else {
        if (arg->newobj_trace == 0) {
            arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg);
            arg->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg);
        }
        rb_tracepoint_enable(arg->newobj_trace);
        rb_tracepoint_enable(arg->freeobj_trace);
    }

    return Qnil;
}
trace_object_allocations_stop click to toggle source

停止跟踪对象分配。

请注意,如果调用了 ::trace_object_allocations_start n 次,则在调用 ::trace_object_allocations_stop n 次后,跟踪将停止。

static VALUE
trace_object_allocations_stop(VALUE self)
{
    struct traceobj_arg *arg = get_traceobj_arg();

    if (arg->running > 0) {
        arg->running--;
    }

    if (arg->running == 0) {
        if (arg->newobj_trace != 0) {
            rb_tracepoint_disable(arg->newobj_trace);
        }
        if (arg->freeobj_trace != 0) {
            rb_tracepoint_disable(arg->freeobj_trace);
        }
    }

    return Qnil;
}

公共实例方法

dump(obj, output: :string) click to toggle source

将 Ruby 对象的内容以 JSON 格式转储。

output 可以是以下之一::stdout:file:string 或 IO 对象。

  • :file 表示将输出转储到临时文件并返回相应的 File 对象;

  • :stdout 表示打印转储并返回 nil

  • :string 表示返回包含转储的字符串;

  • 如果提供了 IO 对象的实例,则输出将写入该对象,并返回该对象。

此方法仅预期在 C Ruby 中工作。这是一个实验性方法,可能会发生变化。特别是,函数签名和输出格式在未来版本的 ruby 中不保证兼容。

# File objspace/lib/objspace.rb, line 28
def dump(obj, output: :string)
  out = case output
  when :file, nil
    require 'tempfile'
    Tempfile.create(%w(rubyobj .json))
  when :stdout
    STDOUT
  when :string
    +''
  when IO
    output
  else
    raise ArgumentError, "wrong output option: #{output.inspect}"
  end

  ret = _dump(obj, out)
  return nil if output == :stdout
  ret
end
dump_all(output: :file, full: false, since: nil, shapes: true) 点击切换源代码

将 ruby 堆的内容转储为 JSON。

output 参数与 dump 的相同。

full 必须是布尔值。如果为 true,则转储所有堆槽,包括空槽 (T_NONE)。

since 必须是非负整数或 nil

如果 since 是正整数,则仅转储该代和更新代的对象。当前代可以使用 GC::count 访问。未启用对象分配跟踪而分配的对象将被忽略。有关更多信息和示例,请参阅 ::trace_object_allocations

如果 since 被省略或为 nil,则转储所有对象。

shapes 必须是布尔值或非负整数。

如果 shapes 是正整数,则仅转储比提供的形状 ID 更新的形状。可以使用 RubyVM.stat(:next_shape_id) 访问当前形状 ID。

如果 shapesfalse,则不转储任何形状。

要仅转储在特定点之后分配的对象,可以组合 sinceshapes

ObjectSpace.trace_object_allocations
GC.start
gc_generation = GC.count
shape_generation = RubyVM.stat(:next_shape_id)
call_method_to_instrument
ObjectSpace.dump_all(since: gc_generation, shapes: shape_generation)

此方法仅预期在 C Ruby 中工作。这是一个实验性方法,可能会发生变化。特别是,函数签名和输出格式在未来版本的 ruby 中不保证兼容。

# File objspace/lib/objspace.rb, line 84
def dump_all(output: :file, full: false, since: nil, shapes: true)
  out = case output
  when :file, nil
    require 'tempfile'
    Tempfile.create(%w(rubyheap .json))
  when :stdout
    STDOUT
  when :string
    +''
  when IO
    output
  else
    raise ArgumentError, "wrong output option: #{output.inspect}"
  end

  shapes = 0 if shapes == true
  ret = _dump_all(out, full, since, shapes)
  return nil if output == :stdout
  ret
end
dump_shapes(output: :file, since: 0) 点击切换源代码

将 ruby 形状树的内容转储为 JSON。

output 参数与 dump 的相同。

如果 since 是正整数,则仅转储比提供的形状 ID 更新的形状。可以使用 RubyVM.stat(:next_shape_id) 访问当前形状 ID。

此方法仅预期在 C Ruby 中工作。这是一个实验性方法,可能会发生变化。特别是,函数签名和输出格式在未来版本的 ruby 中不保证兼容。

# File objspace/lib/objspace.rb, line 116
def dump_shapes(output: :file, since: 0)
  out = case output
  when :file, nil
    require 'tempfile'
    Tempfile.create(%w(rubyshapes .json))
  when :stdout
    STDOUT
  when :string
    +''
  when IO
    output
  else
    raise ArgumentError, "wrong output option: #{output.inspect}"
  end

  ret = _dump_shapes(out, since)
  return nil if output == :stdout
  ret
end