|
That's right. The garbage collection can't guarantee that anything will be available - think of the case that your finalizer is the absolute last thing that is done in your application before it shuts down, all other resources have been reclaimed including all the members inside your class. That's what you have to deal with. The purpose of a finalizer is to give you one last chance to free any native resources that your class may use. One case is that you decide that you want a class with a Hook created by CreateWindowsHookEx and you own that hook. Finalizer implementation would be advised because you are using a native resource directly, and you don't want to leak that resource. As long as the finalizer exists, the runtime will call it, you don't need to. One special case is when you release the resource in your Dispose method, at then end of the Dispose, make a call to GC.SupressFinalize and the finalizer won't get called. Getting the idea?
If your class is using a built in .NET class that owns native resources, you can be pretty sure that .NET takes care of the finalizer for you. IF you implement a finalizer, the runtime calls it. The same holds true for the finalizers that .NET is using. Oh, one last thing, if you do decide to use a native resource by handle, look into .NET's SafeHandle classes, and that will shed some insight on what you're doing.
Again
1) If you implement a finalizer, the runtime will call it when it's being reclaimed by the garbage collector - UNLESS you have explicitly made a call to GC.SupressFinalize.
2) Implement a finalizer when you are using Native Resources, notably Handles.
3) You can never be sure what other data will be available when your finalizer is called.
4) Get Lutz's Reflector, and see how .NET does it's finalizers. I won't answer questions about Reflector, stfw.
Scott P
"Run for your life from any man who tells you that money is evil. That sentence is the leper's bell of an approaching looter." --Ayn Rand
|
|
|
|
|
Cool, Scott!
I agree with you in Finalizer we should only deal with releasing of native resource. I have got CLR via C# book, and referred the chapter 20, GC chapter. For the managed resource in Finalizer, it is suggested we only access the ones which do not have Finalizer method, because if there is finalizer method, the GC order of current object and referred object is not decided.
(section "Using Finalization with Managed Resources").
Do you agree or have any comments?
(So, our conclusion is, dealing with unmanaged resource and manged resource without Finalizer?)
regards,
George
|
|
|
|
|
That's it. Bravo.
"Run for your life from any man who tells you that money is evil. That sentence is the leper's bell of an approaching looter." --Ayn Rand
|
|
|
|
|
Thanks carbon_golem,
It is clear now.
regards,
George
|
|
|
|
|
Hi Scott,
Scott Dorman wrote: 1. The order of finalization is non-determistic, so there is no guarantee that if you access another object from within your finalizer that it will still be available.
2. If by "wrapped objects" you mean an object that your are internally maintaining, it is safe under certain circumstances.
The wrapped object I mean member variable of a class. From your above comments, I think your points are, it is not safe to access member variable of a class inside Finalizer method.
But from this link, it seems it is safe to access, since all the referred objects are retained live in Finalier. This why I am confused.
I think your points are conflicting with what I mentioned above and quoted below. Any comments?
http://msdn2.microsoft.com/en-us/library/ms973837.aspx#dotnetgcbasics_topic2[^]
--------------------
"How Finalization Affects Collection"
Since the internal object pointers must remain valid, not only will the objects directly needing finalization linger in memory but everything the object refers to, directly and indirectly, will also remain in memory. If a huge tree of objects was anchored by a single object that required finalization, then the entire tree would linger, potentially for a long time as we just discussed."
--------------------
regards,
George
|
|
|
|
|
I see your point and I think I understand where you are getting confused.
Even though the internal object pointers remain valid, there is still no guarantee that when you go to access them in the finalizer they will still be valid. From my article[^], Since finalizers run non-deterministically, there is no ordering among them so you can't rely on them being executing in a specific order. You should not access any finalizable objects your type may have a reference to because they may have already been finalized. As a result, you should only free unmanaged resources that your object owns.
Scott.
—In just two days, tomorrow will be yesterday.
—Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[ Forum Guidelines] [ Articles] [ Blog]
|
|
|
|
|
Cool, Scott!
From learning in this forum, I think the best practice should be (also mentioned in the book CLR via C# version 2),
1. We can access and free unmanaged resource;
2. Base class Finalizer (code implicitly generarted);
3. Manages resource without Finalizer;
It is not safe to access,
Manages resource with Finalizer, since Finalizer execution order are non-determistic.
Any comments? Agree?
regards,
George
|
|
|
|
|
Glad you were able to get what you needed. I think that overall I would agree with those statements.
Thanks,
Scott.
Scott.
—In just two days, tomorrow will be yesterday.
—Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[ Forum Guidelines] [ Articles] [ Blog]
|
|
|
|
|
"overall",
Any disagree with Jeffrey's pointer.
regards,
George
|
|
|
|
|
George_George wrote: Any disagree with Jeffrey's pointer.
Nope, just like to keep my options open.
Scott.
—In just two days, tomorrow will be yesterday.
—Hey, hey, hey. Don't be mean. We don't have to be mean because, remember, no matter where you go, there you are. - Buckaroo Banzai
[ Forum Guidelines] [ Articles] [ Blog]
|
|
|
|
|
It is cool, Scott!
Thanks for your help and patience on this thread.
regards,
George
|
|
|
|
|
Hello everyone,
The conclusion, root in managed heap is always alive. Is always correct?
thanks in advance,
George
|
|
|
|
|
George_George,
You seem to be asking ALOT of questions. Might it be better to just get a book?
Regards,
Gareth.
|
|
|
|
|
Hi Gareth,
I have the book Programming C# and CLR VIA C#, but what I asked is not explicitly mentioned in the books. This is what I studied and thought, so I want to come here to listen to gurus like you about whether my understanding is correct.
Any comments?
regards,
George
|
|
|
|
|
Well what's the point of this forum? I have a book on PHP yet I still ask a lot of PHP questions at phpfreaks.com
|
|
|
|
|
Hi George,
these are all starting points when a GC scans for reachable objects:
- the root of the heap (or every heap if there are many holding managed objects)
- the current stack pointer for every thread in the process
- the CPU registers (holding data for the current thread, unless the GC itself is
running in a thread, so the current thread does not belong to the process of interest)
From each of these everything that could be a reference needs to be followed, just like
a web crawler would do when you give it one or more URLs.
Every object that one does not find with the above is declared dead and can be collected;
doing it this way avoids any problems with cyclic references (A holds ref to B, and B holds
ref to A; both are dead if the above algorithm never leads to either A or B).
Luc Pattyn [Forum Guidelines] [My Articles]
This month's tips:
- before you ask a question here, search CodeProject, then Google;
- the quality and detail of your question reflects on the effectiveness of the help you are likely to get;
- use PRE tags to preserve formatting when showing multi-line code snippets.
|
|
|
|
|
Thanks Luc,
1.
Luc Pattyn wrote: the root of the heap (or every heap if there are many holding managed objects)
Sorry, I am confused, GC scans from the root of the heap (I thin it should scan from lowest address of the heap to the highest address of the heap), and scans for what? You only mentioned from where, but never mentioned what to do.
2.
Luc Pattyn wrote: the current stack pointer for every thread in the process
Confusion the same as in (1) -- scan from current stack pointer to do what? Do you mean from the current stack pointer to scan for all available stack local variables?
regards,
George
|
|
|
|
|
Hi George,
1.
I was inaccurate for the heap; most systems use a variation of this scheme:
- stop all threads;
- walk the heap ONLY to mark all objects as dead;
- then perform the algorithm based on the thread stacks (and CPU registers),
marking the live objects as not-dead (see below);
- resume all threads;
- walk the heap to delete all objects that are still marked dead.
There are different schemes (e.g. for better real-time behavior, not really a Windows thing),
for better performance (with generations, implemented in MS .NET), etc. Some are completely
different (e.g. based on reference counts, which is very hard to get right
in cyclic situations). The last step (delete) can be delegated to another (low-prio)
thread.
2.
stack is easy: for each new thread the stack pointer is initialized to some value ("stack
base") and every time something gets pushed onto the stack, the pointer moves away from
that base (either up or down, depends on the CPU architecture).
So the GC will search the stack from its current position towards the stack base,
and threat every 4 or 8 bytes as a potential reference. If it falls outside the heaps,
if is definitely not a reference, hence it gets skipped. If it falls inside the range
of one of the heaps, some extra fields are checked, and if they match it is considered
an object, hence marked alive.
Luc Pattyn [Forum Guidelines] [My Articles]
This month's tips:
- before you ask a question here, search CodeProject, then Google;
- the quality and detail of your question reflects on the effectiveness of the help you are likely to get;
- use PRE tags to preserve formatting when showing multi-line code snippets.
|
|
|
|
|
Thanks Luc,
Sorry I have a stupid and basic question. Threads are of different stacks, right? Means different stack pointer and stack base (not only for .Net threads, but also Windows native threads)?
regards,
George
|
|
|
|
|
Of course, each thread needs its own stack, since threads can be switched in and out
at (almost) any point during their execution, so they must keep their call chain information
independent of each other.
Therefore the kernel will unload and reload all the CPU registers, including the
stack pointer, when switching from one thread to another.
Luc Pattyn [Forum Guidelines] [My Articles]
This month's tips:
- before you ask a question here, search CodeProject, then Google;
- the quality and detail of your question reflects on the effectiveness of the help you are likely to get;
- use PRE tags to preserve formatting when showing multi-line code snippets.
|
|
|
|
|
Thanks Luc,
I read your points before again. Your approach of checking whether an object is live or not in heap is to check whether there are any reference from stack.
I agree but I am not sure whether it is enough in general situations. Why you do not check whether there are reference from not only stack variable, but also from heap variable -- to decide whether an object is live or not?
--------------------
I was inaccurate for the heap; most systems use a variation of this scheme:
- stop all threads;
- walk the heap ONLY to mark all objects as dead;
- then perform the algorithm based on the thread stacks (and CPU registers),
marking the live objects as not-dead (see below);
- resume all threads;
- walk the heap to delete all objects that are still marked dead.
There are different schemes (e.g. for better real-time behavior, not really a Windows thing),
for better performance (with generations, implemented in MS .NET), etc. Some are completely
different (e.g. based on reference counts, which is very hard to get right
in cyclic situations). The last step (delete) can be delegated to another (low-prio)
thread.
--------------------
regards,
George
|
|
|
|
|
Hi George,
an object is alive if there is a (theoretical) probability that one of the process threads
will touch the object. Now where is a thread's knowledge about objects? it is in the data
it knows to access, i.e. the stack (containing stack frames from earlier methods that lead
to the invocation of the current method, and containing local variables for the current
method). It is not in other data, the thread would not know how to find other data, remember
there is no such thing as global variables in OO, so everything the thread is supposed to
work on must be given to it or created by it.
Of course, as soon as an object A is marked "alive", all its data members must also be
inspected; object A could contain references to other objects B, C, ... and being alive itself,
those would automatically be alive too.
Example: a local variable of type List may be passed in as an argument to a method,
and the List may contain any number of Control objects; each of those Controls in turn
would have names, texts, fonts, whatever.
Typical app example: a windows app starting up has one thread, no objects (apart from the
command line args that are passed to static main). It creates a new MyForm
then passes it to Application.Run to make it visible and give it a message pump.
The form may create new threads and pass parameters to them, etc. So new objects get
created and most of the time somehow linked to existing objects through the class members
of MyForm and others.
But there is no magic way to keep objects alive; if none of the threads has any reference
to a specific object, then none of them will be able to touch it ever again, hence the
object is dead.
Well actually there are some methods such as GC.KeepAlive() to mark objects for special cases,
e.g. when the managed world wants to pass an object to unmanaged (native) code and wants
to object to stay alive even when the managed side no longer holds a reference to it.
Final remark: the cyclic reference example I gave earlier (A holds ref to B, B holds ref to A,
none of the threads knows about the existence of A and B, hence both A and B are dead)
proves that you cannot simply walk the heap and decide about live and dead for objects,
since doing so would keep A and B alive no matter what.
Luc Pattyn [Forum Guidelines] [My Articles]
This month's tips:
- before you ask a question here, search CodeProject, then Google;
- the quality and detail of your question reflects on the effectiveness of the help you are likely to get;
- use PRE tags to preserve formatting when showing multi-line code snippets.
|
|
|
|
|
Hi Luc,
Your reply is great! However I do not fully agree with two more comments.
1.
Luc Pattyn wrote: there is no such thing as global variables in OO
We can have global/static variables in C#. I think such kinds of variables are exception to your thread-touched live rule. Means, those global/static variables will always live from process begin to process ends. Any comments?
2.
Luc Pattyn wrote: proves that you cannot simply walk the heap and decide about live and dead for objects,
since doing so would keep A and B alive no matter what.
I do not agree. I think we can still prove A and B are dead since we can not touch them from any threads. Why do you think we can not prove anything?
regards,
George
|
|
|
|
|
Hi George,
1.
yes, static data members of a class are globals once the class got loaded, and as such
they remain alive as long as the class does; which is as long as the AppDomain does where
the class got loaded; which is until you unload the AppDomain. (By default there is just
the one AppDomain, and you may not even be aware of it).
So, yes, all these static members form another base for the GC the look for live objects.
I see you are getting the picture quite well.
2.
if you were to try and prove the island formed by objects A and B is reachable/unreachable
(and hence alive/dead), you would have to build the entire reference graph and study its
connectivity. If you find several loose groups, you would still have to find out which
groups are reachable (from the stack and the globals) and which are not. Simply looking
at four objects interconnected two-by-two (i.e. A---B and C---D) cannot possibly tell
you which group is alive or dead. So there is nothing much to be gained by studying the
heap itself.
Luc Pattyn [Forum Guidelines] [My Articles]
This month's tips:
- before you ask a question here, search CodeProject, then Google;
- the quality and detail of your question reflects on the effectiveness of the help you are likely to get;
- use PRE tags to preserve formatting when showing multi-line code snippets.
|
|
|
|
|
Thanks Luc,
1.
In your sample before,
--------------------
Final remark: the cyclic reference example I gave earlier (A holds ref to B, B holds ref to A,
none of the threads knows about the existence of A and B, hence both A and B are dead)
--------------------
Do you mean we can not identify whether A and B are live or not? Why we can not identify? I think from your algorithm, at the beginning, mark all objects as dead, then if no variables refer A or B, they will be dead. It is easy to jusge live and dead in the situation you mentioned above, why you think we can not judge?
2.
In C# all the variables must be wrapped in class {}, so it is why you think there is no global variable in C# -- all belong to class context?
regards,
George
|
|
|
|
|