gc.KeepAlive - is this the reality of garbage collection

R

Rolf Welskes

Hello,
I have a problem with garbage collection.

An msdn example says generally the following:

I have a class TestCls.
With a Property MyProp.

Then normally I would write:

void MyFunc()
{
TestCls tc = new TestCls();
int u = tc.MyProp;

.... no further use of tc ....
}

The example says this would not work, because the gc could work (finalizer
called) before tc.MyProp has finished.
This is a catastroph.
The example says I had to write:

void MyFunc()
{
TestCls tc = new TestCls();
int u = tc.MyProp;

.... no further use of tc ....
gc.KeepAlive(tc);
}
Think about this, if you have many such objeckts and calls in a method.

Furthermore:
I have tested the example of the msdn: gc Class/ GcMethodes/KeepAlive
Methode
which is at
ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.NETDEVFX.v20.en/cpref2/html/M_System_GC_KeepAlive_1_b4c5a2da.htm
in Msdn VS 2005.
I mean the FIRST example.
I have a computer with 2 cpus (intel dualcore), but the example does not
works as described.

So question is it reality?
Have I to use in this case (only manged code, no interop) the
KeepAlive-Method?
(I only mean this sample with pure dotnet, if I use interop the use of
KeepAlive is clear for me).
Thank you for any help.
Rolf Welskes
 
J

Jon Skeet [C# MVP]

Rolf Welskes said:
I have a problem with garbage collection.

An msdn example says generally the following:

I have a class TestCls.
With a Property MyProp.

Then normally I would write:

void MyFunc()
{
TestCls tc = new TestCls();
int u = tc.MyProp;

.... no further use of tc ....
}

The example says this would not work, because the gc could work (finalizer
called) before tc.MyProp has finished.

Well, it depends what you mean by "work".
This is a catastroph.

It entirely depends on the situation. There are very few situations in
which it's actually a problem.

So question is it reality?

Yes, but a reality which very rarely affects people.

It's usually only an issue if you have a finalizer which may do
something like closing a handle, and that handle is used in a method
which may still be executing when the finalizer executes.

I can't remember the last time I wrote a finalizer...
 
P

Peter Duniho

[...]
void MyFunc()
{
TestCls tc = new TestCls();
int u = tc.MyProp;

.... no further use of tc ....
}

The example says this would not work, because the gc could work
(finalizer called) before tc.MyProp has finished.
This is a catastroph.

I admit that Jon's answer is the safest. I suppose there's some bizarre
scenario in which the code you posted might do something you didn't expect.

However, just because the class instance referenced by "tc" isn't
referenced after that line, that doesn't mean that the GC could clean it
up before the assignment to "u" is done. If the "get" for the property
actually needs something in the instanced referenced by "tc", then the
"get" itself has references to the instance which would prevent
collection. If the "get" doesn't actually need something in the instance,
then it's fine for the instance to be collected before the "get" returns,
because it's not actually using it.

Either way, the code works fine.

I don't see any "catastrophe" here. Frankly, any time you think the
language introduces a "catastrophe", you should question your own
understanding of the language. After all, if the language really did have
such catastrophes in it, it's highly unlikely that it would be in such
wide use.

Pete
 
J

Jon Skeet [C# MVP]

Peter Duniho said:
I admit that Jon's answer is the safest. I suppose there's some bizarre
scenario in which the code you posted might do something you didn't expect.

However, just because the class instance referenced by "tc" isn't
referenced after that line, that doesn't mean that the GC could clean it
up before the assignment to "u" is done. If the "get" for the property
actually needs something in the instanced referenced by "tc", then the
"get" itself has references to the instance which would prevent
collection.

There's slightly more to it than that. The getter could use some field
within the instance to start with, and then do a bunch of other things.
After it's last used the instance, the instance can be garbage
collected.

The reason this becomes a problem is when you have something like:

IntPtr handleInstanceVariable;

void DoSomething()
{
// Last reference to anything in the instance
IntPtr handle = handleInstanceVariable;

// Call to a static method
DoSomethingWithHandle(handle);
}

~WhateverType()
{
Close(handle);
}

Clearly DoSomething() doesn't want the handle to be closed during the
call to DoSomethingWithHandle, but the garbage collector doesn't know
that... so you'd need to put a call to GC.KeepAlive at the end of
DoSomething().

I suspect that the optimisations that can be made with the memory model
make this situation even worse, but I don't know for sure - it depends
at what point the CLR determines (for each method) that the method no
longer uses anything from the instance.

There's a blog entry about all of this written by Chris Brumme, which
is where I first learned about it. Worth a read:
http://blogs.msdn.com/cbrumme/archive/2003/04/19/51365.aspx
 
P

Peter Duniho

[...]
~WhateverType()
{
Close(handle);
}

I think you mean "Close(handleInstanceVariable)", right?

I agree that the scenario you describe is an issue. I just don't think it
qualifies as a catastrophic defect in .NET. :)

As near as I can tell, the problematic scenarios are generally similar to
the one you describe, where some unmanaged resource is involved. As long
as the reference is to a managed resource, the GC can track it. And the
problem exists not in the caller to the "get" method, but rather the class
using the unmanaged resource itself. So while a bug could show up in the
example the OP posted, the problem wouldn't be with the code he posted,
but rather due to a buggy class used by that code.

I don't mean to minimize the importance of understanding these issues.
Obviously they are important, are not always intuitive, and need to be
taken into consideration when dealing with the relevant situations. But
as a general rule, they don't come up very often, and don't reflect any
sort of serious defect in .NET. IMHO, to fix .NET so that the issues
didn't exist (e.g. use a more conservative algorithm for determining
object lifetime, or somehow track references to unmanaged objects) would
hurt .NET more than it helps.

Pete
 
J

Jon Skeet [C# MVP]

Peter Duniho said:
[...]
~WhateverType()
{
Close(handle);
}

I think you mean "Close(handleInstanceVariable)", right?

Yes. Doh. (That's what I get for not writing a sample that compiles :)
I agree that the scenario you describe is an issue. I just don't think it
qualifies as a catastrophic defect in .NET. :)

Oh absolutely. It's the kind of thing one wouldn't expect without
knowing about it though. Before I read about it, I'd always assumed
that an object wouldn't be collected while a method was running "on"
that object. Reality was a surprise :)
As near as I can tell, the problematic scenarios are generally similar to
the one you describe, where some unmanaged resource is involved. As long
as the reference is to a managed resource, the GC can track it. And the
problem exists not in the caller to the "get" method, but rather the class
using the unmanaged resource itself. So while a bug could show up in the
example the OP posted, the problem wouldn't be with the code he posted,
but rather due to a buggy class used by that code.

Exactly. The fix (calling GC.KeepAlive) is within the class itself, not
within the caller.
I don't mean to minimize the importance of understanding these issues.
Obviously they are important, are not always intuitive, and need to be
taken into consideration when dealing with the relevant situations. But
as a general rule, they don't come up very often, and don't reflect any
sort of serious defect in .NET. IMHO, to fix .NET so that the issues
didn't exist (e.g. use a more conservative algorithm for determining
object lifetime, or somehow track references to unmanaged objects) would
hurt .NET more than it helps.

I'm not sure about the last bit - granted I'm far from an expert on GC,
but I doubt that there are that many situations where it's actually
positively desirable to collect an object while a method is running
"on" it. Why can't "this" just automatically be a root as far as the GC
is concerned? Maybe there are some significant use cases I haven't
considered.
 
J

Jon Skeet [C# MVP]

Jon Skeet said:
I'm not sure about the last bit - granted I'm far from an expert on GC,
but I doubt that there are that many situations where it's actually
positively desirable to collect an object while a method is running
"on" it. Why can't "this" just automatically be a root as far as the GC
is concerned? Maybe there are some significant use cases I haven't
considered.

I've thought of one possible problem - inlining of instance methods
which just call static ones. The GC might not know it's even in the
instance method...
 
B

Barry Kelly

Jon said:
Peter Duniho said:
As long
as the reference is to a managed resource, the GC can track it. [...]
IMHO, to fix .NET so that the issues
didn't exist (e.g. use a more conservative algorithm for determining
object lifetime, or somehow track references to unmanaged objects) would
hurt .NET more than it helps.

I'm not sure about the last bit - granted I'm far from an expert on GC,
but I doubt that there are that many situations where it's actually
positively desirable to collect an object while a method is running
"on" it. Why can't "this" just automatically be a root as far as the GC
is concerned? Maybe there are some significant use cases I haven't
considered.

'this' isn't a root in the cases where the GC collects the instance even
while executing code from its methods, because the 'this' instance is no
longer reachable from any roots, including registers, stack, static
fields etc.

To fix the problem, the JIT codegen would have to make sure it never
deregisters the 'this' value even if it is not used for the remainder of
a method, or alternatively push it onto the stack, so the GC can find
it. When I think about it, it could be a reasonable candidate for a fix
- the performance impact would have to be measured of course, but I
suspect it wouldn't be large.

I believe the precaution is only necessary for instance methods defined
on types which have a finalizer, but of course a not-loaded-yet
descendant might add a finalizer...

-- Barry
 
J

Jon Skeet [C# MVP]

Barry Kelly said:
'this' isn't a root in the cases where the GC collects the instance even
while executing code from its methods, because the 'this' instance is no
longer reachable from any roots, including registers, stack, static
fields etc.

Exactly - that's my point. This situation would be easier if it *were*
a root.
To fix the problem, the JIT codegen would have to make sure it never
deregisters the 'this' value even if it is not used for the remainder of
a method, or alternatively push it onto the stack, so the GC can find
it. When I think about it, it could be a reasonable candidate for a fix
- the performance impact would have to be measured of course, but I
suspect it wouldn't be large.

I suspect inlining would be a problem. If I call foo.SomeMethod() which
just calls a static method, passing in a field value from foo, then
that could easily be inlined currently - but not if the GC needs to
know that foo.SomeMethod() is really executing.
I believe the precaution is only necessary for instance methods defined
on types which have a finalizer, but of course a not-loaded-yet
descendant might add a finalizer...

True. Just another reason to make classes sealed unless you've
specifically designed them to be derived from :)

(I must go round my miscutil classes and make them sealed where
appropriate some time...)
 
B

Barry Kelly

Jon said:
I suspect inlining would be a problem.

I don't agree. The compiler needs to do lifetime analysis of variables
to determine a register allocation strategy. To prevent deregistering,
or to cause it to spill out to the stack, all that is required is
creating a phantom use at the end of all basic blocks that exit the
function. Inlining should handle this use appropriately, otherwise
they'd have a bug if the use was genuine and was evaluated in an
expression.

An expensive (presuming the JIT compiler doesn't have clever knowledge
about it) way of doing this would be to inject an implicit call to
GC.KeepAlive(this) after evaluating the expression in a 'return'
statement, but before the IL ret code is emitted; and that includes
implicit return by dropping off the end of the function, and void
returns where there isn't an expression.

An IL opcode would probably be better. And there would still be problems
with tail. call, but if you're dealing with tail. call, you ought to
intuitively know that your frame is no longer on the call stack etc.

BTW, I write compilers for a living :)

-- Barry
 
J

Jon Skeet [C# MVP]

Barry Kelly said:
I don't agree. The compiler needs to do lifetime analysis of variables
to determine a register allocation strategy. To prevent deregistering,
or to cause it to spill out to the stack, all that is required is
creating a phantom use at the end of all basic blocks that exit the
function. Inlining should handle this use appropriately, otherwise
they'd have a bug if the use was genuine and was evaluated in an
expression.

That's true.
An expensive (presuming the JIT compiler doesn't have clever knowledge
about it) way of doing this would be to inject an implicit call to
GC.KeepAlive(this) after evaluating the expression in a 'return'
statement, but before the IL ret code is emitted; and that includes
implicit return by dropping off the end of the function, and void
returns where there isn't an expression.

An IL opcode would probably be better. And there would still be problems
with tail. call, but if you're dealing with tail. call, you ought to
intuitively know that your frame is no longer on the call stack etc.

I'm not sure whether an IL opcode would be necessary - just a change to
what the JIT works with the GC. Unless, of course, you wanted different
languages to have different semantics.
BTW, I write compilers for a living :)

Ah :)
 
J

Jeffrey Tan[MSFT]

Hi Rolf ,

Besides other community member's replies, below is my comment:

You only need to use GC.KeepAlive() method while dealing with unmanaged
resource/code. If you are using pure managed code without unmanaged
interop, there is no need to keep the managed object alive. Actually, if
you do not deal with unmanaged resource, there is no need to implement
IDispose interface or Finalizer.

While dealing with unmanged resource/code, you only need to use
GC.KeepAlive if the unmanaged code is executed asynchronously with your
managed code(such as in background thread). This is because the reference
to you managed object is alive during the entire scope in that
method(unless you set the object reference to null explicitly). If the
unmanaged code finished using the unmanaged resource synchronously in the
method, there is no need to keep the object alive. Actually, this is a rare
situation.

Anyway, if you still have anything unclear, please feel free to tell me,
thanks.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 
J

Jon Skeet [C# MVP]

Jeffrey Tan said:
Besides other community member's replies, below is my comment:

You only need to use GC.KeepAlive() method while dealing with unmanaged
resource/code.

I'd put it as only needing it while implementing a finalizer.
If you are using pure managed code without unmanaged
interop, there is no need to keep the managed object alive. Actually, if
you do not deal with unmanaged resource, there is no need to implement
IDispose interface or Finalizer.

Well, hang on. There are other reasons for implementing IDisposable
than merely collecting unmanaged resources, and while I wouldn't
implement a finalizer without *directly* having access to unmanaged
resources, it's still good practice to implement IDisposable if you
have a reference to something else which implements IDisposable (eg a
stream). In that case you don't directly have access to the unmanaged
resource, but you still want to be able to ask the managed type to
clean up the unmanaged resource in a timely manner.
While dealing with unmanged resource/code, you only need to use
GC.KeepAlive if the unmanaged code is executed asynchronously with your
managed code(such as in background thread).

Well, the finalizer thread is always a separate thread anyway, so that
doesn't really cut the scenarios down.
 
J

Jeffrey Tan[MSFT]

Hi Jon ,

#1, Yes, your comment is right. Actually, this scenario means indirectly
using the unmanaged resource.

#2, Oh, yes, I have performed a reading to cbrumme's WebLog entry below. It
is really informative to reference lifetime and GC.KeepAlive. It seems that
I originally have some misunderstanding on the reference lifetime. I would
second a highly recommendation to Rolf this blog entry:
"Lifetime, GC.KeepAlive, handle recycling "
http://blogs.msdn.com/cbrumme/archive/2003/04/19/51365.aspx

Jon: thank you very much for the sharing!

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 
R

Rolf Welskes

Hello,
ok, what you say would be fine.
BUT:
Look in the sample in the msdn for KeepAlive.
Here you find sample one:
This is pure managed code with one thread, the second thread is only for
visualize the results.
And in this sample you have:
....
MyCls a = new MyCls()
a.fu()
GC.KeepAlive(a); //this sample is what make the trouble !!!


Thank you for any help.
Rolf Welskes
 
J

Jeffrey Tan[MSFT]

Hi Rolf ,

Thanks for your feedback.

I can not find "MyCls" class in the KeepAlive MSDN link, I assume you mean
the following sample in MSDN, yes?

Example ex = new Example();
byte[] res = ex.Hash;
...
GC.KeepAlive(ex);

Yes, this sample is not confined to the unmanaged code scenario; it is used
to address one subtle JIT issue with GC. The Microsoft Achitect Chris
Brumme has discussed this subtle issue in his WebLog below:
"Lifetime, GC.KeepAlive, handle recycling "
http://blogs.msdn.com/cbrumme/archive/2003/04/19/51365.aspx

You may give it a detailed reading.

Hope this is clear. Thanks.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 
R

Rolf Welskes

Hello,
thank you for your information.
I have read the article you mentioned.
But the problem is to important.

Think we have
ClsA with a finalizer and unmanaged resources.
ClsA.Fu() may be a funktion in this class.

A. I would agree with:
void Fu()
{
......... do something with unmanged resources

GC.KeepAlive(this);
}

In a client witch uses this class I need NOT to use KeepAlive.
I simple could write:
ClsA a = new ClsA();
a.Fu(); //Thats all no KeepAlive.

B. Bad would be:
if I am in client code which uses ClsA
and had:

......
ClsA a = new ClsA();
a.F();
GC.KeepAlive(a);

Bad because I would not know when witch class of a not own library has such
situations.

So the questions is:
If I use a library, and I do not use any unmanged resources and no
finalizers in my code,
can I be sure that I need not to use KeepAlive?
Clearly, this question means, is all in the library as described in point A.
(not point B.).
Clearly, you can only say this for the dot net library (other libraries of
any vendors, so are a risk).
Thank you for help.
Best Regards
Rolf Welskes
 
J

Jeffrey Tan[MSFT]

Hi Rolf ,

Thanks for your feedback!

Yes, I see your concern. In situation B, it is the responsibility of the
class library author to take care of the finalizer thread/application
thread race condition issue. So if you are the client, I do not think you
should be borther to call GC.KeepAlive(a);

If your library classes do not consume any unmanaged resource, there is no
such problem of using GC.KeepAlive(a). This is because all the managed
objects can always be monitored by the GC reachability feature. It is
impossible that one thread is still using a managed object while GC thread
is trying to destroy it. This is the situation we are sure to be safe.

If you still have anything unclear, please feel free to tell me, thanks.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 
R

Rolf Welskes

Hello,
thank you for your help.
I think I can live with this, but I wish any time it would be better.
Thank you again and best regards
Rolf Welskes
 
J

Jeffrey Tan[MSFT]

Hi Rolf,

Yes, I see your concern with this issue. However, this type of design is
determined by CLR dev team, which goes out of my scope. Anyway, if you have
strong concern over it, please feel free to let me known, I will help you
to notify the CLR team, I think they are happy to hear from real concern
customer to improve CLR. Thanks.

Best regards,
Jeffrey Tan
Microsoft Online Community Support
==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top