Garbage collection and define_finalizer

Hi.

I was wondering if running the (mark and sweep?) garbage collector manually is supposed to collect (and call define_finalizer procs on) objects with no remaining references. I would expect that the answer is yes, but it doesn't seem to actually work that way.

When I run the script below (based on some examples online that apparently avoid some of the problems with define_finalizer), the output suggests that the finalizer for an allocated and unused object of class A isn't actually being called until *after* the garbage collection is complete and the program has reached the end. This suggests that calling the garbage collector doesn't actually cause unreferenced objects to be reaped immediately.

Is there a fault in my assumptions or the script below?

If you wrap the A.new in a loop instead, eventually it'll free up the other instances of A en-masse (ie. calling the GC doesn't seem to do it, but flooding memory until the GC triggers does).

I have seen similar behavior in a large embedded Ruby program that I am working on. In this case the Ruby objects in question have instance variables that reference textures, and I really need the finalizer to be called when all of the references to the textures are lost, so as to free up the texture memory. At the moment they are being finalised at program exit, when the available texture memory has long run out. This isn't good, and it means I need to rewrite every potential bit of this code to use manual reference counting.

So basically: If the garbage collector is called, are objects with no remaining references supposed to be reaped during the call, and their defined finalizers called? Whatever the answer- why is that? Is there an official word on how this is supposed to work, and what can (and can't) be relied upon?

Any thoughts?

Cheers,
Garthy

···

---

#!/usr/bin/ruby -w
###!/packages/ruby/bin/ruby -w

class A

   def initialize
     ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)
     #ObjectSpace.define_finalizer(self, self.class.finalize2)
     @a = 1
     $stderr.print "Init A\n"
   end

   def self.finalize(id)
     $stderr.print "Finalise called for: #{id}.\n"
   end

   def self.finalize2
     proc {$stderr.print "Finalise called.\n"}
   end

end

def do_gc
   GC.start
   #ObjectSpace.garbage_collect
end

def foo
   a = A.new
   nil
end

foo

$stderr.print "Starting garbage collection.\n"
do_gc
$stderr.print "Finished garbage collection.\n"

The finalizer behavior is undefined at the language level because it is runtime-dependent. Try running your code on JRuby or Rubinius. Each has a different GC than MRI and will thus behave differently. The GC in MRI is also different between 1.8 and 1.9 when it was replaced with a more efficient mechanism.

You can't rely on the GC calling your finalizers when the object is reaped. Each runtime will have different behavior.

I have a project that uses a finalizer to deallocate some native memory (allocated via FFI). I see the same behavior as you describe on MRI. On JRuby and Rubinius the finalizer is called "closer in time" to when the object is reaped but I don't know for certain that it is happening in the same cycle as the GC. For all I know it's pushing the finalizer reference onto another queue somewhere and getting to it during its idle time.

So, try another runtime or modify your code to do refcounting.

cr

···

On Nov 29, 2011, at 6:43 AM, Garthy D wrote:

Hi.

I was wondering if running the (mark and sweep?) garbage collector manually is supposed to collect (and call define_finalizer procs on) objects with no remaining references. I would expect that the answer is yes, but it doesn't seem to actually work that way.

When I run the script below (based on some examples online that apparently avoid some of the problems with define_finalizer), the output suggests that the finalizer for an allocated and unused object of class A isn't actually being called until *after* the garbage collection is complete and the program has reached the end. This suggests that calling the garbage collector doesn't actually cause unreferenced objects to be reaped immediately.

Is there a fault in my assumptions or the script below?

If you wrap the A.new in a loop instead, eventually it'll free up the other instances of A en-masse (ie. calling the GC doesn't seem to do it, but flooding memory until the GC triggers does).

I have seen similar behavior in a large embedded Ruby program that I am working on. In this case the Ruby objects in question have instance variables that reference textures, and I really need the finalizer to be called when all of the references to the textures are lost, so as to free up the texture memory. At the moment they are being finalised at program exit, when the available texture memory has long run out. This isn't good, and it means I need to rewrite every potential bit of this code to use manual reference counting.

So basically: If the garbage collector is called, are objects with no remaining references supposed to be reaped during the call, and their defined finalizers called? Whatever the answer- why is that? Is there an official word on how this is supposed to work, and what can (and can't) be relied upon?

I was wondering if running the (mark and sweep?) garbage collector manually
is supposed to collect (and call define_finalizer procs on) objects with no
remaining references. I would expect that the answer is yes, but it doesn't
seem to actually work that way.

There are no guarantees whatsoever that *any* GC run will collect
particular unreachable instances. The only guarantee is that all
finalizers are invoked eventually (unless of course in case of
catastrophic crash) - even if it is at process termination time.

When I run the script below (based on some examples online that apparently
avoid some of the problems with define_finalizer), the output suggests that
the finalizer for an allocated and unused object of class A isn't actually
being called until *after* the garbage collection is complete and the
program has reached the end. This suggests that calling the garbage
collector doesn't actually cause unreferenced objects to be reaped
immediately.

Exactly.

Is there a fault in my assumptions or the script below?

It's in your assumptions. Note also that MRI won't bother to do any
GC run at all as long as memory stays below a certain threshold.

If you wrap the A.new in a loop instead, eventually it'll free up the other
instances of A en-masse (ie. calling the GC doesn't seem to do it, but
flooding memory until the GC triggers does).

I have seen similar behavior in a large embedded Ruby program that I am
working on. In this case the Ruby objects in question have instance
variables that reference textures, and I really need the finalizer to be
called when all of the references to the textures are lost, so as to free up
the texture memory. At the moment they are being finalised at program exit,
when the available texture memory has long run out. This isn't good, and it
means I need to rewrite every potential bit of this code to use manual
reference counting.

No, that's a bad solution. What you observe might mean that you
simply haven't created enough Ruby garbage for GC to think it needs to
work. I have no idea how you allocate texture memory but if it is in
a C extension written by you I would check whether there is a way to
go through MRI's allocation in order to correct MRI's idea of used
memory. Implementation of Ruby's String might be a good example for
that.

Another solution is to use transactions like File.open with a block
does. Then you know exactly when the texture is not used any more and
can immediately release it (in "ensure"). Whether that is a viable
option depends on the design of your application. See here:

http://blog.rubybestpractices.com/posts/rklemme/002_Writing_Block_Methods.html

So basically: If the garbage collector is called, are objects with no
remaining references supposed to be reaped during the call, and their
defined finalizers called? Whatever the answer- why is that? Is there an
official word on how this is supposed to work, and what can (and can't) be
relied upon?

GC's prefer to decide themselves when and how they collect deadwood.
There are usually only very few guarantees (see JVM spec for an
example) in order to allow VM implementors maximum freedom and room
for optimization. The only hard guarantee is that an object won't be
collected as long as it is strongly reachable.

Kind regards

robert

···

On Tue, Nov 29, 2011 at 1:43 PM, Garthy D <garthy_lmkltybr@entropicsoftware.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Hi Robert,

Thankyou for the detailed reply. :slight_smile:

I was wondering if running the (mark and sweep?) garbage collector manually
is supposed to collect (and call define_finalizer procs on) objects with no
remaining references. I would expect that the answer is yes, but it doesn't
seem to actually work that way.

There are no guarantees whatsoever that *any* GC run will collect
particular unreachable instances. The only guarantee is that all
finalizers are invoked eventually (unless of course in case of
catastrophic crash) - even if it is at process termination time.

I suspected this might be the case- and it's certainly not an unreasonable assumption to make of a GC.

If you wrap the A.new in a loop instead, eventually it'll free up the other
instances of A en-masse (ie. calling the GC doesn't seem to do it, but
flooding memory until the GC triggers does).

I have seen similar behavior in a large embedded Ruby program that I am
working on. In this case the Ruby objects in question have instance
variables that reference textures, and I really need the finalizer to be
called when all of the references to the textures are lost, so as to free up
the texture memory. At the moment they are being finalised at program exit,
when the available texture memory has long run out. This isn't good, and it
means I need to rewrite every potential bit of this code to use manual
reference counting.

No, that's a bad solution. What you observe might mean that you
simply haven't created enough Ruby garbage for GC to think it needs to
work. I have no idea how you allocate texture memory but if it is in
a C extension written by you I would check whether there is a way to
go through MRI's allocation in order to correct MRI's idea of used
memory. Implementation of Ruby's String might be a good example for
that.

Your assumption re the C extension for texture memory is pretty-much spot on. :slight_smile:

Re texture memory, it's a little more complicated than a standard allocation. Some memory will be the standard allocated sort, but some will be on the video card, and they're effectively coming from different "pools" (or heaps), neither of which I'll have direct control over. Unless the Ruby GC directly understands this concept (I don't know if it does, but I'm guessing not), I'm not going to be able to use Ruby to manage that memory. Unfortunately, the problem goes a bit beyond just textures, as I'm wrapping a good chunk of a 3D engine in Ruby objects. That's my problem to worry about though.

And that's assuming I can redirect the allocation calls anyway- I'm not sure if I can. It'd be nice to be able to inform the Ruby GC of allocated memory (or an estimate, if it keeps changing) without actually leaving the GC to allocate it. Please correct me if I'm wrong, but I'm assuming this can't be done, and you must use the ALLOC/ALLOC_N-style functions?

Another solution is to use transactions like File.open with a block
does. Then you know exactly when the texture is not used any more and
can immediately release it (in "ensure"). Whether that is a viable
option depends on the design of your application. See here:

http://blog.rubybestpractices.com/posts/rklemme/002_Writing_Block_Methods.html

Thankyou for the link. It does not appear to be suitable for say textures in my specific case (they are long-lived and freed at a later time), but may help with some of the other problems I need to solve.

So basically: If the garbage collector is called, are objects with no
remaining references supposed to be reaped during the call, and their
defined finalizers called? Whatever the answer- why is that? Is there an
official word on how this is supposed to work, and what can (and can't) be
relied upon?

GC's prefer to decide themselves when and how they collect deadwood.
There are usually only very few guarantees (see JVM spec for an
example) in order to allow VM implementors maximum freedom and room
for optimization. The only hard guarantee is that an object won't be
collected as long as it is strongly reachable.

This is completely reasonable of course.

Unfortunately it causes a lot of problems in my case, as I had assumed one thing from general reading on the topic, and observed another. That's my problem to deal with though, not anyone else's. Still, there seems to be lot of information online that talks about Ruby performing mark and sweep, either stating outright that unreferenced objects are freed on first GC, or heavily implying it at least. From your description, and my observations, this information appears to be incorrect. Basically, there seems to be a lot of misinformation about what the GC is doing. Apart from the source, is there some place where the correct behaviour is discussed, that could be referred to instead, particularly if someone is suggesting that the MRI GC implementation should immediately clean up these references on next GC (which, as we've established, isn't accurate)?

Garth

···

On 30/11/11 00:07, Robert Klemme wrote:

On Tue, Nov 29, 2011 at 1:43 PM, Garthy D > <garthy_lmkltybr@entropicsoftware.com> wrote:

Hi Chuck,

Thankyou for the fast reply. :slight_smile:

Hi.

I was wondering if running the (mark and sweep?) garbage collector manually is supposed to collect (and call define_finalizer procs on) objects with no remaining references. I would expect that the answer is yes, but it doesn't seem to actually work that way.

When I run the script below (based on some examples online that apparently avoid some of the problems with define_finalizer), the output suggests that the finalizer for an allocated and unused object of class A isn't actually being called until *after* the garbage collection is complete and the program has reached the end. This suggests that calling the garbage collector doesn't actually cause unreferenced objects to be reaped immediately.

Is there a fault in my assumptions or the script below?

If you wrap the A.new in a loop instead, eventually it'll free up the other instances of A en-masse (ie. calling the GC doesn't seem to do it, but flooding memory until the GC triggers does).

I have seen similar behavior in a large embedded Ruby program that I am working on. In this case the Ruby objects in question have instance variables that reference textures, and I really need the finalizer to be called when all of the references to the textures are lost, so as to free up the texture memory. At the moment they are being finalised at program exit, when the available texture memory has long run out. This isn't good, and it means I need to rewrite every potential bit of this code to use manual reference counting.

So basically: If the garbage collector is called, are objects with no remaining references supposed to be reaped during the call, and their defined finalizers called? Whatever the answer- why is that? Is there an official word on how this is supposed to work, and what can (and can't) be relied upon?

The finalizer behavior is undefined at the language level because it is runtime-dependent. Try running your code on JRuby or Rubinius. Each has a different GC than MRI and will thus behave differently. The GC in MRI is also different between 1.8 and 1.9 when it was replaced with a more efficient mechanism.

Thankyou for the information. Leaving the GC/finalizer behaviour undefined is reasonable- it just causes me problems in my particular circumstances. :confused:

I haven't experimented with other Ruby implementations (just the MRI), but it is good to know that other implementations may also handle it differently. Basically: I can assume very little about how the GC might behave.

You can't rely on the GC calling your finalizers when the object is reaped. Each runtime will have different behavior.

Yes. Unfortunate for me, but understandable.

I have a project that uses a finalizer to deallocate some native memory (allocated via FFI). I see the same behavior as you describe on MRI. On JRuby and Rubinius the finalizer is called "closer in time" to when the object is reaped but I don't know for certain that it is happening in the same cycle as the GC. For all I know it's pushing the finalizer reference onto another queue somewhere and getting to it during its idle time.

It's nice to know that I'm not going crazy. :wink: Because a great deal of information online suggests that the memory is cleaned up immediately (on GC), I had assumed this was the case, and that I must have had a bug or unknown reference to certain objects somewhere in my code. As you can probably imagine, that took some time to track down.

So, try another runtime or modify your code to do refcounting.

I was hoping to avoid needing to layer reference counting on top of things, but I might not have a great deal of choice in the matter. I'll just have to think of ways to make it work well in my particular circumstances. Darn.

Garth

···

On 29/11/11 23:42, Chuck Remes wrote:

On Nov 29, 2011, at 6:43 AM, Garthy D wrote:

That is, in fact, what Hotspot does. If you get a thread dump from a
running OpenJDK JVM, you'll see something like this:

"Finalizer" daemon prio=5 tid=0x000000010086f800 nid=0x106c80000 in
Object.wait() [0x0000000106c7f000]
   java.lang.Thread.State: WAITING (on object monitor)
  at java.lang.Object.wait(Native Method)
  - waiting on <0x00000007f5965e70> (a java.lang.ref.ReferenceQueue$Lock)
  at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
  - locked <0x00000007f5965e70> (a java.lang.ref.ReferenceQueue$Lock)
  at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
  at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)

It waits for finalizable object references to be pushed onto its queue
and then runs their finalizers.

- Charlie

···

On Tue, Nov 29, 2011 at 7:12 AM, Chuck Remes <cremes.devlist@mac.com> wrote:

I have a project that uses a finalizer to deallocate some native memory (allocated via FFI). I see the same behavior as you describe on MRI. On JRuby and Rubinius the finalizer is called "closer in time" to when the object is reaped but I don't know for certain that it is happening in the same cycle as the GC. For all I know it's pushing the finalizer reference onto another queue somewhere and getting to it during its idle time.

Thankyou for the detailed reply. :slight_smile:

You're welcome!

What you observe might mean that you
simply haven't created enough Ruby garbage for GC to think it needs to
work. I have no idea how you allocate texture memory but if it is in
a C extension written by you I would check whether there is a way to
go through MRI's allocation in order to correct MRI's idea of used
memory. Implementation of Ruby's String might be a good example for
that.

Your assumption re the C extension for texture memory is pretty-much spot
on. :slight_smile:

:slight_smile:

Re texture memory, it's a little more complicated than a standard
allocation. Some memory will be the standard allocated sort, but some will
be on the video card, and they're effectively coming from different "pools"
(or heaps), neither of which I'll have direct control over. Unless the Ruby
GC directly understands this concept (I don't know if it does, but I'm
guessing not), I'm not going to be able to use Ruby to manage that memory.
Unfortunately, the problem goes a bit beyond just textures, as I'm wrapping
a good chunk of a 3D engine in Ruby objects. That's my problem to worry
about though.

Sounds tricky indeed.

And that's assuming I can redirect the allocation calls anyway- I'm not sure
if I can. It'd be nice to be able to inform the Ruby GC of allocated memory
(or an estimate, if it keeps changing) without actually leaving the GC to
allocate it. Please correct me if I'm wrong, but I'm assuming this can't be
done, and you must use the ALLOC/ALLOC_N-style functions?

I'm not such a good source for this as the source (or Matz). So you
probably better look elsewhere for a definitive answer. Maybe for
your issue it is sufficient to just reduce the threshold from which on
Ruby starts considering GC at all. I can't really tell.

Unfortunately it causes a lot of problems in my case, as I had assumed one
thing from general reading on the topic, and observed another. That's my
problem to deal with though, not anyone else's. Still, there seems to be lot
of information online that talks about Ruby performing mark and sweep,
either stating outright that unreferenced objects are freed on first GC, or
heavily implying it at least. From your description, and my observations,
this information appears to be incorrect. Basically, there seems to be a lot
of misinformation about what the GC is doing. Apart from the source, is
there some place where the correct behaviour is discussed, that could be
referred to instead, particularly if someone is suggesting that the MRI GC
implementation should immediately clean up these references on next GC
(which, as we've established, isn't accurate)?

I am not aware of any. I haven't searched extensively though. But we
might need to update locations you found to reflect the truth.
Ideally someone from core team would do a writeup which explains how
GC works - at least for MRI. Chances are also that behavior has
changed between 1.8 and 1.9 as these are quite different internally.

Kind regards

robert

···

On Wed, Nov 30, 2011 at 4:11 AM, Garthy D <garthy_lmkltybr@entropicsoftware.com> wrote:

On 30/11/11 00:07, Robert Klemme wrote:

On Tue, Nov 29, 2011 at 1:43 PM, Garthy D >> <garthy_lmkltybr@entropicsoftware.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Hi Robert,

Thankyou for the detailed reply. :slight_smile:

You're welcome!

What you observe might mean that you
simply haven't created enough Ruby garbage for GC to think it needs to
work. I have no idea how you allocate texture memory but if it is in
a C extension written by you I would check whether there is a way to
go through MRI's allocation in order to correct MRI's idea of used
memory. Implementation of Ruby's String might be a good example for
that.

Your assumption re the C extension for texture memory is pretty-much spot
on. :slight_smile:

:slight_smile:

Re texture memory, it's a little more complicated than a standard
allocation. Some memory will be the standard allocated sort, but some will
be on the video card, and they're effectively coming from different "pools"
(or heaps), neither of which I'll have direct control over. Unless the Ruby
GC directly understands this concept (I don't know if it does, but I'm
guessing not), I'm not going to be able to use Ruby to manage that memory.
Unfortunately, the problem goes a bit beyond just textures, as I'm wrapping
a good chunk of a 3D engine in Ruby objects. That's my problem to worry
about though.

Sounds tricky indeed.

It certainly keeps things interesting. :wink:

And that's assuming I can redirect the allocation calls anyway- I'm not sure
if I can. It'd be nice to be able to inform the Ruby GC of allocated memory
(or an estimate, if it keeps changing) without actually leaving the GC to
allocate it. Please correct me if I'm wrong, but I'm assuming this can't be
done, and you must use the ALLOC/ALLOC_N-style functions?

I'm not such a good source for this as the source (or Matz). So you
probably better look elsewhere for a definitive answer.

I had a dig around the source (gc.c in particular) to see if I could find any answers, but I'm afraid it's beyond me at this point. I'd hazard a guess that the functionality doesn't yet exist, but I could very well be completely wrong. It's just a guess at this stage.

Maybe for
your issue it is sufficient to just reduce the threshold from which on
Ruby starts considering GC at all. I can't really tell.

On thinking about it, I really need to solve the problem "properly", particularly for large memory-guzzling things such as textures and 3D models. As an experiment I set up a means to force an object to drop its data ahead of time (where "drop" is defined as deleting the user data, but keeping the Ruby object), effectively invalidating it. For textures (and similar entities), the plan is to give each entity that requires one its own copy of a wrapped smart pointer. Thus the textures will be freed once every entity holding a texture object has either manually dropped its copy, or the GC has collected any references I've missed (and the plan of course is to try not to miss any references!). I've got it working on simple objects at this point. I'll have to recode a few things to get it going for textures and models, but this is *probably* the solution I'll go for. I'll have to see how it turns out in practice.

However, once this is done for the bigger entities, there will still be quite a large number of small entities with minimal additional memory footprint that Ruby is unaware of. Reducing the threshold may be enough to solve the problem for the majority of these small entities- thankyou for the suggestion. :slight_smile:

Unfortunately it causes a lot of problems in my case, as I had assumed one
thing from general reading on the topic, and observed another. That's my
problem to deal with though, not anyone else's. Still, there seems to be lot
of information online that talks about Ruby performing mark and sweep,
either stating outright that unreferenced objects are freed on first GC, or
heavily implying it at least. From your description, and my observations,
this information appears to be incorrect. Basically, there seems to be a lot
of misinformation about what the GC is doing. Apart from the source, is
there some place where the correct behaviour is discussed, that could be
referred to instead, particularly if someone is suggesting that the MRI GC
implementation should immediately clean up these references on next GC
(which, as we've established, isn't accurate)?

I am not aware of any. I haven't searched extensively though. But we
might need to update locations you found to reflect the truth.
Ideally someone from core team would do a writeup which explains how
GC works - at least for MRI. Chances are also that behavior has
changed between 1.8 and 1.9 as these are quite different internally.

I'll keep an eye out for such a thing if it appears.

Garth

···

On 30/11/11 23:51, Robert Klemme wrote:

On Wed, Nov 30, 2011 at 4:11 AM, Garthy D > <garthy_lmkltybr@entropicsoftware.com> wrote:

On 30/11/11 00:07, Robert Klemme wrote:

On Tue, Nov 29, 2011 at 1:43 PM, Garthy D >>> <garthy_lmkltybr@entropicsoftware.com> wrote: