How to count existing instances of a class in ruby

2019-01-25 21:50发布

问题:

Here's an idea from this question: Upon object creation, increment a class variable. When object gets collected, decrement it. As you can observe, finalizer is called, and @@no_foo gets decremented. But when I query it a moment later, decrement is gone. Seems that value is going only up, never down (if I create two objects, it will show 2). Am I missing something obvious?

class Foo
  @@no_foo = 0

  def initialize
    puts 'creating object'
    @@no_foo += 1
    ObjectSpace.define_finalizer(self, proc { self.delete })
  end

  def delete
    puts 'deleting object'
    @@no_foo # => 1
    @@no_foo -= 1
    @@no_foo # => 0
  end

  def self.no_foo
    @@no_foo # => 0, 1
  end
end

Foo.no_foo # => 0
f = Foo.new
f = nil

GC.start
Foo.no_foo # => 1

# >> creating object
# >> deleting object

回答1:

It can work, but there's circular reference in finalization. Your finalizer depends on the binding of an object that should be collected. See this solution.

class Foo
  @@no_foo = 0

  def initialize
    @@no_foo += 1
    ObjectSpace.define_finalizer(self, Foo.method(:delete))
  end

  def self.delete id # also this argument seems to be necessary
    @@no_foo -= 1
  end

  def self.no_foo
    @@no_foo
  end
end

Foo.no_foo # => 0
1000.times{Foo.new}
Foo.no_foo # => 1000

GC.start
Foo.no_foo # => 0


回答2:

Finalization is not happening when you think it should in the code you provided.

For example, if you change that one line to:

ObjectSpace.define_finalizer(self, proc do; puts "self is type #{self.class.name} and equals #{self.inspect}"; self.delete; end)

Then notice how it does nothing (even if I sit there and wait a while) until I kill irb:

... (entered class definition from above with that define_finalizer)
1.9.3-p392 :021 > Foo.no_foo # => 0
 => 0 
1.9.3-p392 :022 > f = Foo.new
creating object
 => #<Foo:0x007fb5730f3e00> 
1.9.3-p392 :023 > f = nil
 => nil 
1.9.3-p392 :024 > 
1.9.3-p392 :025 > GC.start
 => nil 
1.9.3-p392 :026 > Foo.no_foo # => 1
 => 1 
1.9.3-p392 :027 > ^D
self is type Foo and equals #<Foo:0x007fb5730f3e00>
deleting object

So the first assumption may be that GC was not invoked. But, lets look at it using GC::Profiler:

1.9.3p392 :001 > GC::Profiler.enable
... (entered class definition from above)
1.9.3p392 :022 > puts GC::Profiler.result
GC 17 invokes.
Index    Invoke Time(sec)       Use Size(byte)     Total Size(byte)         Total Object                    GC Time(ms)
 => nil 
1.9.3p392 :023 > Foo.no_foo # => 0
 => 0 
1.9.3p392 :024 > f = Foo.new
creating object
 => #<Foo:0x007fe2fc806808> 
1.9.3p392 :025 > puts GC::Profiler.result
GC 17 invokes.
Index    Invoke Time(sec)       Use Size(byte)     Total Size(byte)         Total Object                    GC Time(ms)
 => nil 
1.9.3p392 :026 > f = nil
 => nil 
1.9.3p392 :027 > puts GC::Profiler.result
GC 17 invokes.
Index    Invoke Time(sec)       Use Size(byte)     Total Size(byte)         Total Object                    GC Time(ms)
 => nil 
1.9.3p392 :028 > GC.start
 => nil 
1.9.3p392 :029 > puts GC::Profiler.result
GC 18 invokes.
Index    Invoke Time(sec)       Use Size(byte)     Total Size(byte)         Total Object                    GC Time(ms)
    1               0.161               997280              2257680                56442         3.96199999999999352696
 => nil 
1.9.3p392 :030 > Foo.no_foo # => 1
 => 1 
1.9.3p392 :031 > ^D
deleting object

So, it looks like the GC is getting invoked when you ask it to, but it is not finalizing the Foo instance until irb exit.