I Am a Happy Janitor – Part 1: Finding garbage


Indeed what I do is very much like the job of the janitors – like the ones who clean your building, or janitors you see at a food court, or yourself when you are taking care of garbage at your house. Doubtful you say? Let me prove it to you.


 


Finding garbage


 


What do you do when you are having lunch with your friends at a food court and suddenly you need to go somewhere for a short while in the middle of it (like, you asked for hot sauce but were given not-so-hot-sauce and you went back to the cashier to tell him how it’s so not up to your standard)? Then the nice janitor guy comes along and he sees your plate and tries to take it (you were so hungry you already ate most of your food before your remembered to use the sauce…). What happens now?


 


One of your friends says to the janitor guy, “my friend is not finished yet.”. The janitor then goes away without taking your plate.


 


Same principle for the GC, the difference is GC’s friends tell it what’s not garbage, ie, what’s live (seriously..who wants garbage…). GC’s friends include:


 


·         JIT


·         EE stack walker


·         Handle table


·         Finalize queue


 


What happens is this:


 


GC says: I need to start a collection on gen X…My super friends please report to me what is live now for gen X.


 


JIT says: Roger that. I will take care of the live roots on managed stack frames. When I jitted methods I had already built up a table that tracked which locals were alive at which point so I will report the ones that are currently live to you. I am quite aggressive at determining if something should be dead or not in retail builds – locals could be treated as dead as soon as I determine it’s not in use anymore (not guaranteed). However in debuggable builds I am much more lenient – I will let all locals live till the end of the scope to make debugging more convenient.


 


EE stack walker says: I can take care of the locals on the unmanaged frames when managed frames call into the EE (for lock implementation and whatnot).


 


Finalize queue says: Indeed, the almighty me brought back some dead objects back to life so I am telling you about these objects.


 


Handle table says: Reporting handles in use in gen X and all its youngest generations now.


 


GC waits patiently while the super friends are reporting what’s live.


 


The only time GC needs to participate in finding what’s live is when GC needs to find out if there are objects in older generations that are referring to objects in younger generations. For example if GC is collecting gen 1, it will need to go find out if there are any gen2 objects that are referring to gen1 and gen0.

Comments (21)

  1. Miral says:

    Presumably it’s not all that strict about generations — after all, a gen0 object could be holding the only reference to a gen2 object…

    And to go back to your anecdote, you’re basically saying that if one of the GC’s friends says "I dunno" then the janitor goes ahead and removes the plate anyway.  Though this is where the anecdote falls down, because that’s really the only way that the GC could sensibly operate, and hopefully its friends are more reliable 🙂

  2. Niall says:

    Did the behaviour regarding reporting locals as unused before the end of the method change for v2.0? Using 1.1, I used to have some code for a unit test that would fail due to a weak reference not being alive before the end of a method in which it was declared. The unit test was run by the build process in debug mode, without a debugger attached, and would fail reliably. However, debugging the unit test never showed the problem.

    So it appeared that for 1.1, the JIT would report locals that were still in scope based on whether the debugger was attached, not whether the build was debug/release. However, I’m unable to reproduce this behaviour in 2.0 – it seems the only time this happens now is when the app is built in release mode and run without a debugger.

    Just curious to know if this logic has indeed been tweaked for 2.0 such that it never reports locals early in debug mode?

  3. Swami says:

    Hi Maoni,

    I am in the process of developing a new application and am analysing about the memory management options in .NET 2.0. Can you pls give me your contact info (email-id) so that i can mail you my question separately.

    Thanks a lot in advance,

    Swami

  4. maoni says:

    Miral, GC’s friends don’t say "I don’t know." That was an incorrect analogy that I made. GC’s friends always know. I should remove that part.

  5. maoni says:

    Niall, first of all, I’d like to make sure that you are not assuming that JIT either tracks the exact lifetime or always extends the lifetime to end of method/scope. Even in retail builds (with optimization) if the method has too many locals JIT will not track all of them (so the lifetimes of the ones that aren’t tracked are extended to the end of the method).

    Secondly, yes there was indeed change in behavior between 1.1 and 2. In 1.1 running under the debugger implies debuggable code (with no optimization) is the default but in 2.0 they are orthogonal. When you are running debuggable code the lifetimes are extended to the end of the scope.

  6. maoni says:

    Miral, I re-read your comment – actually you got it backwards – I was saying if the janitor comes to take your plate and your friends are discusssing whether you are done or not, the janitor will NOT take your plate. But in any case, let’s make it perfectly clear and not use that case 🙂

  7. maoni says:

    Swami, if you have questions, feel free to post them in the comments. This way other readers may benefit from them too. Thanks.

  8. Mark E. says:

    Maoni,

    I have a suggestion on the future development of the GC and the memory manager more generally.

    I already openned this discussion with the Mono developers but wanted to suggest it to you as well.

    =============================

    = As sent to the mono list

    =============================

    Hey guys,

    I was about to send this suggestion to Maoni (of the .NET GC – http://blogs.msdn.com/maoni/) and wanted to send it here first, if nothing else, to make the idea less patentable by MS.

    I would like for the whole memory manager system to be componentized and replaceable. This would allow the user to choose what is the right approach for their application. Then, if done well, third-parties could develop alternate memory managers that were geared for specific purposes.

    Here’s my rationale:

    1) In some scenarios, I want a memory manager with explicit finalization. That is a priority in some scenarios. I will take the performance hit to have full reference counting and immediate finalization. The benefit to me is better control of memory and system RAM utilization.

    2) I may want a Workstation version that is memory footprint priority. Running on a Citrix server with 4GB RAM but needing to limit the app to 60MB per client (by customer request) is nearly impossible. Either the customer’s request cannot be met or .NET Framework / Mono cannot be the platform.

    3) I don’t assume that MS will implement the best memory manager for me. Make allowances for others to improve on it.

    Potential issues against it:

    1) If a memory manager could be replaced, I could re-implement one (or modify an OpenSource one) to circumvent DRM. I can see that being why MS won’t do it.

    2) Complexity of the integration.

    3) Not a large enough perceived benefit/need.

    What are your thoughts?

    -Mark E.

  9. maoni says:

    Hi Mark,

    Thanks for your comments. Those are valid options to think about. We had already thought of them before and decided not to pursue them due to consideration regarding programming models and how applications work in general. Regarding your 2nd point, the CLR’s hosting interface does provide some amount of control over memory usage. Given how integral to the runtime the GC is, if there are any issues in the replacement GC, they could severely impair the stability of the system and result in a flood of PSS calls. Due to the fact that this isn’t a very common request and the risk it represents, we don’t believe it adds enough customer value to make it worthwhile for us to provide an option to let users substitute the GC.

  10. Mark E. says:

    Maoni,

    Thanks for the explanation and clarification. I appreciate that you took the time to explain this.

    I am intregued by your statement that, "[…] the CLR’s hosting interface does provide some amount of control over memory usage." I’ve heard nothing of this before and I’ll begin researching this now. Can you provide any additional information or direction into configuring this?

    Thanks,

    -Mark E.

  11. maoni says:

    Hi Mark, take a look at IGCHostControl::RequestVirtualMemLimit:

    http://msdn2.microsoft.com/en-us/library/ms404403.aspx

    The IHostMemoryManager interface may also be of interest to you. For example, if you implement IHostMemoryManager::VirtualAlloc, when GC calls VirtualAlloc it will go through your implementation.

  12. Mark E. says:

    Maoni,

    Thank you for your response. That is an interesting idea. The documentation (understandably) is very thin. I did find the solution to my problem that will not require such explicit memory control.

    For the sake of other who may struggle with this topic, I’ll include our solution…

    My application’s architecture is based on dynamically loading AppDomains which will then dynamically load plugins. All I had to do was to add a specific call to "GC.Collect();" at the point when the AppDomain was unloaded. This keeps the memory growth in check. It’s the perfect time to call it as all the DLLs and objects have been freed with the AppDomain.

    Personally, I think the GC should automatically get called when an AppDomain is unloaded, but I can see why it might not as well. At any rate, this allows us to keep a managable baseline memory footprint that a customer can see and understand.

    Thanks for your help!

    -Mark E.

  13. maoni says:

    Mark, as you said

    >>Personally, I think the GC should automatically get called when an AppDomain is unloaded, but I can see why it might not as well.

    For the CLR since we need our stuff to work for a very wide range of scenarios we don’t do that. As a matter of fact we used to trigger a GC on AD unload but there was perf problems with it so we didn’t do that anymore.

    For your own scenario, if you are completely in control when you unload appdomains and you don’t do it often it’s ok to induce GC yourself. I certainly would not recommand it as a general solution.

  14. Raghu says:

    Hi,

    Do you have any plans to benchmark .net framework 2.0 with BEA WebLogic Real Time  (WLRT) 1.1 . They are boasting "deterministic" GC.  There is a lot of hype out there….

    Can you comment on this…

    Thanks.

    Raghu/..

  15. maoni says:

    Raghu, I can not comment on this since legally we (as in product groups) are not allowed to run benchmarks on our competitor products.

  16. Gourav says:

    Maoni,

    I really appreciate this Weblog. Thanks A lot !!

    I have one windows service which use System.Timers.Timer to Create Process in interval of 5 sec on each elapse Event memory is increasing by 4 KB, I used CLRProfiler and found heap is growing like any thing.

    Please help me to solve this problem ?

  17. maoni says:

    Gourav, so what does CLRProfiler about what contributes to the growth of the heap?

  18. Mike Waldrop says:

    I thought I had a pretty good handle on the GC until I started using the DumpGen command in SOSEX.  In my testing I found that during a GC, the memory in Gen 0 was not being freed.  Or at least that is how it appears.

    I was purposely doing a GC.Collect to see what got left over in trying to see what had some objects locked.

    What I found interesting is this.  Many instances of aspx pages and other objects still existed in Gen 0 after I did a collect.  BUT here is the weird part.

    First I did a GC.Collect then ran the below commands expecting the same result.  I was always told after a GC gen 0 is practically 0.

    This command: !dumpheap -type ASP.MyPage (or any obect)

    showed collection working as expected.   But then I used the SOSEX command of

    !dumpgen 0 -type ASP.MyPage

    and there were tons of instances consuming memory.  Even after doing a collection.  Perhaps I’m thinking of it wrong.  I thought when a gen0 happens object moved to gen1 if needed, object that can be collected are and then compaction.  And the pointer to gen0 reset.

    I assumed the blocks that are removed are actually emptied But maybe this is where I am confused.  If the pointer is moved then maybe those blocks are just overriden and the SOSEX tool however it works is just finding these?

    Seems strange though.  When I create dumps they are in the 200 meg range but yet the eeheap -gc tells me that it’s only a few megs.  And the total bytes in heaps counter jives more with the 200 megs.

    So is the memory in gen 0 actually freed?  Or is the SOSEX tool just showing me incorrect data.

    Oh and Happy New Year, as I see it’s 12:15 am.

    if you could email me that would be great.  jedi1_9_7_3@yahoo.com

  19. Some common causes of OutOfMemoryExceptions in ASP.NET applications and information on how to resolve these exceptions.

  20. Anil says:

    Hi,

    We have an enterprise level system that is currently build on .NET2.0. Lately we shifted the target to .NET 4.0. The system is primarily a desktop system. After shifting the target to .NET 4.0 we have found that the average memory consumed per client has increased substantially, esp. in the CITRIX environment. Yes, the way GC works has been modified in .NET 4.0, but could this be the cause of the higher memory consumed.