Surprisingly the issue I wrote about in "the eventhandlers that made the memory baloon" (Jan 2006) is something that still happens very frequently, I reference it in cases at least a few times a month. Just this last week I had different variations of it crop up in different cases so in this post I will show a different variation, what to look out for and how to identify it.
The issue here is a quick growth in memory, eventually causing OutOfMemory exceptions.
The problem in this case was that the application stored UI controls in session scope, i.e. Labels, DataGrids etc. in order to keep relevant information about the user throughout the session. The data they want to store is not that big, a subset of a dataset, some user related strings etc. and since it was already nicely contained in the controls there was no reason to extract them into a separate object to store in session scope.
If we look at a dropdownlist for example in memory, this is what a dropdownlist contains (the Name column contains the member variable names, and the Type column shows the type of the member variable)
So if I have a dropdownlist that contain all the US states and their abbreviations, and I want to store this in cache to avoid having to rebuild the list, a logical step to take might be to cache the dropdownlist.
However, if I do so, anything that it references will be referenced as long as the dropdownlist is in cache, so it can't be garbage collected. In the case of web controls, they all have a _parent member variable which links back to the parent control, and a _page that links back to the page where the control was originally instantiated. In other words, this means that as long as your control is in cache, you will also be caching the page, any other controls that it uses, any data bindings to data grids etc. etc.
As you can see above you will also be caching the statebag (viewstate), the IDataSource and anything it references, etc. etc. all in all it builds up to quite a bit more data than we originally bargained for, when in reality, what I wanted to store was just the list of states and abbreviations and maybe the selected item (if storing it in session scope).
Whenever you store anything in cache or session scope, make sure that you store just what you need and nothing more. If you store an object that is not a simple type like a string or an int, make sure that you know all about that object and it's member variables so that you know exactly what it is that you will be holding on to. UI objects are never good to store in cache or session scope since they contain quite a bit of extra data about their appearance and location, instead just extracting the ItemsColection and the selected item would be appropriate in the case above.
How you can identify the problem in a memory dump:
You can get a memory dump when memory is high either with Debug Diagnostics 1.1, or by running adplus -hang -pn w3wp.exe (from the debugging tools for windows).
If you open it up in windbg, and load sos (.loadby sos mscorwks) these issues are fairly easy to identify.
Running !dumpheap -stat you will see a lot of System.Web.UI items at the bottom of the output in this case.
And if you run !dumpheap -type aspx or !dumpheap -type ascx to list all the aspx/ascx pages on the heap you will likely find many of them
The next step is to take any of these pages, preferably the ones that are most plentiful like ASP.default_aspx in this case, and !gcroot some of them to figure out why they are still around
If we follow the root chain from the bottom up we see that ASP.default_aspx is a member variable of the DropDownList (the _page member variable) and that this in turn is a member variable of a NameObjectEntry in a hash table in session state. The NameObjectEntry is a key-value pair with the key and the value of a session variable, so through this we can gather that we are storing the DropDownlist in session scope.
If we look at the NameObjectEntry we can also see exactly which session variable it is (LstStates in this case)
In summary, be careful with what you store away,
is the !dumpheap -type aspx specific to a SOS version ?
I tried that and i get 0 items, but in my dumpheap -stat i can see i have a lot aspx
in some versions you have to run !dumpheap -type *aspx*
Do you know in dumpheap -stat what -1 in GEN means ?
0x0a402814 0x18d04bc4 256 -1 _ASP.ReportOpPendentes_aspx
You shouldnt get the gen column with !dumpheap -stat, probably with some other switch for !dumpheap since the gen is only displayed for individual objects.
I believe it means it has already been garbage collected.
You would usually see it if you try to dump out objects outside of the currently used areas of the heap.
If you get it with normal !dumpheap -type ... or !dumpheap -mt ..., make sure that you are using the sos that comes with the framework.
You are right, it was with dumpheap -mt and not -stat. This is something that always confuses me. If it´s already Garbage collected why is it still around ? Because if i do a gcroot i get no root for those objects
it just hasn't been marked as free yet, perhaps it is in the middle of a garbage collection, or there is something wrong so that you are enumerating items that are outside of the bounds of the currently allocated memory.
I would have to look at the exact details to give you any further answers...
AJAX Cool ScriptManager stuff I missed... [Via: Jay Kimble ] ASP.NET ASP.NET Memory: Thou shalt not...
You'd think by now someone would have thought to turn this into a compiler warning or MDA as often as the issue comes up.
many, many thanks. that was exactly our problem here. Now we fixed it with your help!!! Many thanks!
I'm wondering if this technique could be used to create a tool to evaluate the amount of memory required for one user's Session state?
I'm working on an ASP.NET application that stores a lot of data in a Session and I'm concerned about how this will scale as the company adds customers. It would be great to know the exact resources a single user requires. That way, we'd know whether to rebuild or just add memory to cope with expansion.
you can always use something like this
On a related theme.
I have a question regarding the MVP pattern that has become so popular of late.
One of the ideas behind the pattern is passing around a view. Often the view can be a whole ASP.net page.
I was wondering what the performance implications of this are as there must be a lot of information being transfered around that does not need to.
are you talking about MVC (Model View Controller)?
If so, I think the story is a little bit different. You wouldnt be passing around controls that have ties back to the page necessarily. If you do then that might be an issue... to be really honest I haven't really looked into it enough to know what the details are and what the implications would be. Once I get there I'll make sure to post about it though...
Thanks for the reply!
Yes the MVP is a slightly different take on the MVC pattern but they have essentially the same goal; seperation of concerns.
I guess I should stop being lazy and fire up windbg and have a look myself.
Any tips on what to look for?
Would I see this issue if storing Control instances in the HttpContext.Item Collection. I know the Context dies at the end of the request, but would it delay the garbage collection of the control and associated parent?