views:

762

answers:

7

Running into a prickly problem with our web app here. (Asp.net 2.0 Win server 2008)

Our memory usage for the website, grows and grows even though I would expect it to remain at a fairly static level. (We have a small amount of data that gets stored in state).

Wanting to find out what the problem is, I've run a System.GC.Collect(); a few times, taken a memory dump and then loaded this memory dump into WinDbg.

When I do a DumpHeap -Stat I get an inordinately large number on particular type hanging around in memory.

0000064280580b40 713471 79908752 PaymentOption

so, doing a DumpHeap -MT for this type, I get a stack of object references. Picking a random number of these, I do a !gcroot and the command comes back reporting that no references are held to it.

To me, this is exactly when the GC should collect these items, but for some reason they have been left outstanding.

Can anybody offer an explanation as to what might be happening?

+1  A: 

Not without more info on your application. But we ran into some nasty memory problems a long time ago. Do you use ASP.NET caching? As Raymond Chen likes to say, "poor caching strategy is indisitinguishable from a memory leak."

Check out another tool - CLRProfiler.exe - it will help you traverse object reference trees to see where your objects are rooted. This is also good: link text

You've heard this before - if you have to GC.Collect, something is wrong.

n8wrl
No caching, which is the wierd thing that's going on here. We have a third party reporting component that for some reason uses the cache (you can tell from the gcroots that it has)
Lachman
Thanks for the tip on the CLRProfiler, but the problem is that when a TraverseHeap is peformed and the xml written out, the objects that I'm interested in aren't there. (This is what I would expect, since it appears that they do not have any valid roots.)
Lachman
To be clear, I have a button that calls GC.Collect that I press a couple of times before taking a memory dump, it doesn't get call during normal operation.
Lachman
+1  A: 

Is the PaymentOption object created in an asynchronous process, by any chance? I remember something about, if you don't call EndInvoke, you can get problems like this.

overslacked
+2  A: 

Few things:

  1. GC.Collect won't help you do any debugging. The garbage collector is already being called: if any objects were available for collection it would have happened already.
  2. Idle memory on a server is wasted memory. Are you sure memory is being 'leaked', or is it just that the framework is deciding it can keep more things in memroy or keep more memory around for faster access? In this case I suspect you are leaking memory, but it's something to double check for.
  3. It sounds like something you don't expect is keeping a reference to PaymentOption objects. Perhaps a static collection somewhere? Or separate thread?
Joel Coehoorn
pt2: Agree idle memory is wasted, but I'm seeing gigs of ram being used on objects that are not rooted, but don't seem to get collected.
Lachman
pt3: I suspect that's what's going on, but with !gcroot not reporting any references, I'm not sure where to go about finding out what may be keeping hold of them. I guess that's the point of my question.
Lachman
I know this is old now, but I wanted to add something that occured to me and isn't mentioned anywhere else in the question. This could be the large object heap. If there's any chance these objects are > 80,000 bytes you could have a problem with this.
Joel Coehoorn
+2  A: 

Hi there.

You could try using sosex.dll in Windbg, which is an extension written to help with .NET debugging. There is a command named !refs which is similar to !gcroot, in that it will show you all the objects referencing an object, plus it will show all the objects that it too is referencing.

In the example on the author's website, !refs is used against an object and the output looks like this:

0:000> !refs 0000000080000db8 Objects referenced by 0000000080000db8 (System.Threading.Mutex): 0000000080000ef0 32 Microsoft.Win32.SafeHandles.SafeWaitHandle

Objects referencing 0000000080000db8 (System.Threading.Mutex): 0000000080000e08 72 System.Threading.Mutex+<>c__DisplayClass3 0000000080000e50 64 System.Runtime.CompilerServices.RuntimeHelpers+CleanupCode

Hope this helps. Jas.

Thanks for the tip. But I already checked this out and got no love from the !refs command. It reported no roots. grrr
Lachman
A: 

I've been investigating the same issue myself and was asking why objects that had no references were not being collected.

Objects larger than 85,000 bytes are stored on the Large Object Heap, from which memory is freed up less frequently.

http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

A single PaymentOption may not be that big, but are they contained within collections, or are they based on something like a DataSet? You should pick on few instances of the PaymentOption / collection / DataSet and then use the sos !objsize command to see big they are.

Unfortunately this doesn't really answer the question. I like to think I can trust the .net framework to take care of releasing unused memory whenever it needs to. However I see a lot of memory being used by the worker process running the app I am looking at, even when memory looks quite tight on the server.

Dan Malcolm
+1  A: 

Does PaymentObject implement a finalizer by any chance? Does it call a STA COM object?

I'd be curious to see the output of !finalizequeue to see if the count of objects that are showing up on the heap are roughly the amount of any that might waiting to be finalized. Output should probably look something like this:

generation 0 has 57 finalizable objects (0409b5cc->0409b6b0)
generation 1 has 55 finalizable objects (0409b4f0->0409b5cc)
generation 2 has 0 finalizable objects (0409b4f0->0409b4f0)
Ready for finalization 0 objects (0409b6b0->0409b6b0)

If the number of Ready for finalization objects continues to grow, and your certain garbage collections are occuring (confirm via perfmon counters), then it might be a blocked finalizer thread. You might need to take several snapshots over the lifetime of the process (before a recycle) to confirm. I usually rely on the magic number of three, as long as the site is under some sort of load.

A bug in a finalizer can block the finalizer thread and prevent the objects from ever being collected.

If the PaymentOption object calls a legacy STA COM object, then this article ASP.NET Hang and OutOfMemory exceptions caused by STA components might point in the right direction.

Zach Bonham
A: 

FYI, SOS in .NET 4 supports a few new commands that might be of assistance, namely !gcwhere (locate the generation of an objection; sosex's gcgen) and !findroots (does what it says on the tin; sosex's !refs)

Both are documented on the SOS documentation and mentioned on Tess Ferrandez's blog.

Richard Szalay