Collecting garbage

Hi.

I have a doubt about garbage collection:

···

----------------------------------------------------------------------------------------
class CustomObject
  attr_accessor :val, :next
  def initialize(v,n=nil)
    @val = v
    @next = n
  end
  def to_s
    "Object #{@val} (#{self.object_id}) points to #{@next.nil? ? 'nothing' : @next.val} (#{@next.nil? ? '':@next.object_id})"
  end
end

def list
  print "Listing all CustomObject's with ObjectSpace\n"
  print "#{ObjectSpace.each_object(CustomObject) {|v| puts v}} objects found\n\n"
end

c1 = CustomObject.new("1",CustomObject.new("2",CustomObject.new("3")))
c4 = CustomObject.new("4",CustomObject.new("5"))
c6 = CustomObject.new("6")
c1.next.next.next = c1 # comment this and check again
list

c1 = nil
c4.next = nil

GC.start # here I want c1 disappears
sleep(1)
list
----------------------------------------------------------------------------------------

running this program I get

----------------------------------------------------------------------------------------
Listing all CustomObject's with ObjectSpace
Object 6 (-604874926) points to nothing ()
Object 4 (-604874906) points to 5 (-604874896)
Object 5 (-604874896) points to nothing ()
Object 1 (-604874866) points to 2 (-604874856)
Object 2 (-604874856) points to 3 (-604874846)
Object 3 (-604874846) points to 1 (-604874866)
6 objects found

Listing all CustomObject's with ObjectSpace
Object 6 (-604874926) points to nothing ()
Object 4 (-604874906) points to nothing ()
Object 1 (-604874866) points to 2 (-604874856)
Object 2 (-604874856) points to 3 (-604874846)
Object 3 (-604874846) points to 1 (-604874866)
5 objects found
----------------------------------------------------------------------------------------

There's a circular reference there: c1 points to 2 (created internally on 1), 2 points to 3 (created internally on 2), and 3 points to c1, which is a reference at root (?) level there and again points to an internal 2, that points to 3, that points to c1 ...

My question is, when assigning nil to c1, it should not invalidate all the inner objects and becomes available for garbage collection?
On the example above, I assigned nil to c1 and c4.next (5), and after the GC.start I don't have 5, but still have c1 (same id), 2 and 3. On that case, that memory will never be sweeped (free)? Because seems that I'll always have a Object with id -604874856 and will not have a way to refer to it later, for use or free the allocated memory for it.

Thanks.

----------------------------
Eustáquio "TaQ" Rangel
eustaquiorangel@yahoo.com
http://beam.to/taq
Usuário GNU/Linux no. 224050

"Eustaquio Rangel de Oliveira Jr." <eustaquiorangel@yahoo.com> writes:

My question is, when assigning nil to c1, it should not invalidate all
the inner objects and becomes available for garbage collection?
On the example above, I assigned nil to c1 and c4.next (5), and after
the GC.start I don't have 5, but still have c1 (same id), 2 and 3. On
that case, that memory will never be sweeped (free)? Because seems
that I'll always have a Object with id -604874856 and will not have a
way to refer to it later, for use or free the allocated memory for it.

Ruby's GC is lazy and conservative. Lazy meaning, it does not
exhaustively try to do path-finding on each object because for some
objects, path-finding is an expensive process. Conservative meaning,
if it is not doing a path-finding on an object, that object is not
freed.

Hate it or love it. It has both advantages and disadvantages. You have
just shown a disadvantage. But it can be mitigated by exiting the
scope which should make path-finding to those objects cheaper.

class CustomObject
  attr_accessor :val, :next
  def initialize(v,n=nil)
    @val = v
    @next = n
  end
  def to_s
    "Object #{@val} (#{self.object_id}) points to #{@next.nil? ? 'nothing' : @next.val} (#{@next.nil? ? '':@next.object_id})"
  end
end

def list
  print "Listing all CustomObject's with ObjectSpace\n"
  print "#{ObjectSpace.each_object(CustomObject) {|v| puts v}} objects found\n\n"
end

begin # start a new scope so we can exit it later
  c1 = CustomObject.new(1,CustomObject.new(2,CustomObject.new(3)))
  c4 = CustomObject.new(4,CustomObject.new(5))
  c6 = CustomObject.new(6)
  c1.next.next.next = c1 # comment this and check again
  puts "### Initial"
  list
  
  c1 = nil
  c4.next = nil

  GC.start

  puts "### After gc, but still within declaring scope"
  list
end

puts "### Exitted the scope"
list

GC.start # here I want c1 disappears

puts "### After gc, outside of declaring scope"
list

Output:

### Initial
Listing all CustomObject's with ObjectSpace
Object 6 (-604561942) points to nothing ()
Object 4 (-604561932) points to 5 (-604561922)
Object 5 (-604561922) points to nothing ()
Object 1 (-604561912) points to 2 (-604561902)
Object 2 (-604561902) points to 3 (-604561892)
Object 3 (-604561892) points to 1 (-604561912)
6 objects found

### After gc, but still within declaring scope
Listing all CustomObject's with ObjectSpace
Object 6 (-604561942) points to nothing ()
Object 4 (-604561932) points to nothing ()
Object 1 (-604561912) points to 2 (-604561902)
Object 2 (-604561902) points to 3 (-604561892)
Object 3 (-604561892) points to 1 (-604561912)
5 objects found

### Exitted the scope
Listing all CustomObject's with ObjectSpace
Object 6 (-604561942) points to nothing ()
Object 4 (-604561932) points to nothing ()
Object 1 (-604561912) points to 2 (-604561902)
Object 2 (-604561902) points to 3 (-604561892)
Object 3 (-604561892) points to 1 (-604561912)
5 objects found

### After gc, outside of declaring scope
Listing all CustomObject's with ObjectSpace
Object 6 (-604561942) points to nothing ()
Object 4 (-604561932) points to nothing ()
2 objects found

Yohanes Santoso <ysantoso-rubytalk@dessyku.is-a-geek.org> writes:

exiting the scope which should make path-finding to those objects
cheaper.

correction: declaring scope, not variable scope.

YS.

Hi!

Thanks for the answer. :slight_smile:

Ruby's GC is lazy and conservative. Lazy meaning, it does not
exhaustively try to do path-finding on each object because for some
objects, path-finding is an expensive process. Conservative meaning,
if it is not doing a path-finding on an object, that object is not
freed.

Hate it or love it. It has both advantages and disadvantages. You have
just shown a disadvantage. But it can be mitigated by exiting the
scope which should make path-finding to those objects cheaper.

I'll search about lazy and conversative gc's. :slight_smile:
Just another question: if I use a method there, and not a begin/end block,
like this

- --------------------------------------------------------------------------
def test
  c1 = CustomObject.new("1",CustomObject.new("2",CustomObject.new("3")))
  c4 = CustomObject.new("4",CustomObject.new("5"))
  c6 = CustomObject.new("6")
  c1.next.next.next = c1 # comment this and check again
  list
end

test
GC.start
sleep(1)
list
- --------------------------------------------------------------------------

After run this, only c6 remains. It's about scope also?

Thanks!

- ----------------------------
Eust?quio "TaQ" Rangel
eustaquiorangel@yahoo.com

Usu?rio GNU/Linux no. 224050

"Eustaquio Rangel de Oliveira Jr." <eustaquiorangel@yahoo.com> writes:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi!

Thanks for the answer. :slight_smile:

> Ruby's GC is lazy and conservative. Lazy meaning, it does not
> exhaustively try to do path-finding on each object because for some
> objects, path-finding is an expensive process. Conservative meaning,
> if it is not doing a path-finding on an object, that object is not
> freed.
>
> Hate it or love it. It has both advantages and disadvantages. You have
> just shown a disadvantage. But it can be mitigated by exiting the
> scope which should make path-finding to those objects cheaper.

I'll search about lazy and conversative gc's. :slight_smile:
Just another question: if I use a method there, and not a begin/end block,
like this

- --------------------------------------------------------------------------
def test
  c1 = CustomObject.new("1",CustomObject.new("2",CustomObject.new("3")))
  c4 = CustomObject.new("4",CustomObject.new("5"))
  c6 = CustomObject.new("6")
  c1.next.next.next = c1 # comment this and check again
  list
end

test
GC.start
sleep(1)
list
- --------------------------------------------------------------------------

After run this, only c6 remains. It's about scope also?

Funny, I couldn't reproduce what you are seeing. I cut and paste the
code you have and run it:

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
/tmp $ ruby1.8 ./gc.rb
Listing all CustomObject's with ObjectSpace
Object 6 (-604561642) points to nothing ()
Object 4 (-604561622) points to 5 (-604561612)
Object 5 (-604561612) points to nothing ()
Object 1 (-604561582) points to 2 (-604561572)
Object 2 (-604561572) points to 3 (-604561562)
Object 3 (-604561562) points to 1 (-604561582)
6 objects found

Listing all CustomObject's with ObjectSpace
0 objects found
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Nothing remains.

Probably you are seeing c6 remaining because you were playing with
your code like so:

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
def test
  c1 = CustomObject.new("1",CustomObject.new("2",CustomObject.new("3")))
  c4 = CustomObject.new("4",CustomObject.new("5"))
  c6 = CustomObject.new("6")
end

test
GC.start
sleep(1)
list
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Notice that c6 is returned from test, so the object refered to by c6
is now accessible from where you invoke GC.start and that is the
original problem.

YS.

Hi!

Probably you are seeing c6 remaining because you were playing with
your code like so:

Not really, let me paste the full code here:

···

-------------------------------------------------------------------------------
class CustomObject
  attr_accessor :val, :next
  def initialize(v,n=nil)
    @val = v
    @next = n
  end
  def to_s
    "Object #{@val} (#{self.object_id}) points to #{@next.nil? ? 'nothing' : @next.val} (#{@next.nil? ? '':@next.object_id})"
  end
end

def list
  print "Listing all the CustomObject's with ObjectSpace\n"
  print "#{ObjectSpace.each_object(CustomObject) {|v| puts v}} objects found\n\n"
end

def test
  c1 = CustomObject.new("1",CustomObject.new("2",CustomObject.new("3")))
  c4 = CustomObject.new("4",CustomObject.new("5"))
  c6 = CustomObject.new("6")
  c1.next.next.next = c1
  list

  c1 = nil
  c4.next = nil
end

test
GC.start
sleep(1)
list
-------------------------------------------------------------------------------
running
-------------------------------------------------------------------------------
Listing all the CustomObject's with ObjectSpace
Object 6 (-604875086) points to nothing ()
Object 4 (-604875066) points to 5 (-604875056)
Object 5 (-604875056) points to nothing ()
Object 1 (-604875026) points to 2 (-604875016)
Object 2 (-604875016) points to 3 (-604875006)
Object 3 (-604875006) points to 1 (-604875026)
6 objects found

Listing all the CustomObject's with ObjectSpace
Object 6 (-604875086) points to nothing ()
1 objects found
-------------------------------------------------------------------------------

> Funny, I couldn't reproduce what you are seeing.
> Nothing remains.

Maybe a situation like this:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/142834

But the main point of this question you solved: the begin ... end works perfectly.

Thanks!

----------------------------
Eustáquio "TaQ" Rangel
eustaquiorangel@yahoo.com

Usuário GNU/Linux no. 224050

"Eustaquio Rangel de Oliveira Jr." <eustaquiorangel@yahoo.com> writes:

Not really, let me paste the full code here:

Listing all the CustomObject's with ObjectSpace
Object 6 (-604875086) points to nothing ()
1 objects found

Still same thing, I got 0 objects found at the end.

But I'm glad you have satisfied your curiosity.

YS.

I've been following this thread as well as why's post (and the comments) at:

http://redhanded.hobix.com/inspect/stuffingYourHandDownTheDisposal.html

and I'm left confused about 2 things:

1) What sort of scope does begin..end introduce? In the comments to his post, why writes "Variables declared inside a begin..end or inside a block are block local, they perish with the close of the block." But my own experience seems to contradict why's statement. This program does not throw an error:

#!/usr/local/bin/ruby
begin
x = 1
end
p x

2) Why are people getting different results when they run the CustObj garbage collection code? When I run it, Object 1 never gets collected by GC, even after exiting the begin..end scope.

Can anybody help me understand?

Jay

I've been following this thread as well as why's post (and the comments) at:

http://redhanded.hobix.com/inspect/stuffingYourHandDownTheDisposal.html

and I'm left confused about 2 things:

1) What sort of scope does begin..end introduce? In the comments to his post, why writes "Variables declared inside a begin..end or inside a block are block local, they perish with the close of the block." But my own experience seems to contradict why's statement. This program does not throw an error:

#!/usr/local/bin/ruby
begin
x = 1
end
p x

begin does not create a new scope. why made a mistake here.

2) Why are people getting different results when they run the CustObj garbage collection code? When I run it, Object 1 never gets collected by GC, even after exiting the begin..end scope.

Can anybody help me understand?

The GC is very finicky, so you may or may not get the same behavior as other people do. That's part of the nature of Ruby's conservative GC.

···

On 13 Jul 2005, at 06:43, Jay Cotton wrote:

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04