模块 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 或 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 点击以切换源代码

计算每种符号类型的符号。

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

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

注意:返回的哈希的内容是实现定义的。 它将来可能会更改。

此方法仅有望与 C Ruby 一起使用。

在这个版本的 MRI 上,它们有 3 种符号类型(和 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 或 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 或 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'ed 内存大小。

此方法可以通过以下 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 或 nil 点击以切换源代码
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_SPECIAL_CONST_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 点击以切换源代码
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 } 点击以切换源代码

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 点击以切换源代码

清除记录的跟踪信息。

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() 点击以切换源代码
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 点击以切换源代码

开始跟踪对象分配。

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 点击以切换源代码

停止跟踪对象分配。

请注意,如果 ::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) 点击以切换源代码

将 ruby 对象的内容转储为 JSON。

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

  • :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 是正整数,则仅转储比提供的 shape id 新的形状。 可以使用 RubyVM.stat(:next_shape_id) 访问当前 shape_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 是正整数,则仅转储比提供的 shape id 新的形状。 可以使用 RubyVM.stat(:next_shape_id) 访问当前 shape_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