7

I have an ASP.NET / C# web application that is using a lot of memory.

ANTS Memory Profiler and PerfMon both show my Gen 0 heap growing rapidly to about 1 GB in size during Application_Start. I read here that the PerfMon counter for Gen 0 Heap is actually showing the "budget" for Gen 0, not the size (which I take to mean not all that memory is part of the private working set of the process?). However the ANTS profiler does show about 700 MB of "unused memory allocated to .NET", and this does seem to be part of the private working set of the process (as reported in taskmgr). I am guessing this large amount of unused memory is related to the large Gen 0 heap.

What is happening in Application_Start while this happens is that I'm in a while loop reading about a million rows from a SqlDataReader. These are being used to populate a large cache for later use. Given this, the obvious culprit for the large amount of unused memory was large object heap fragmentation, but I don't think this is the case as I'm pre-allocating more than is needed for my large cache object. To be certain, I even tried commenting out the part of the loop that actually adds to my cache object; it made no difference in the amount of unused memory allocated.

As a test, I tried frequently forcing garbage collection of gen 0 during the loop (against all recommendations, I know), and this caused the size of the gen0 heap to stay down around 128 MB and also resulted in only a few MB of unused free memory. But it also maxed out my CPU and made Application_Start take way too long.

My questions are:

1) What can cause the reported size of the Gen 0 Heap to grow so large?

2) Is this a problem? In particular, could it be causing a large amount of unused space to be allocated to .NET?

3) If so, what should I do to fix it? If I can't prevent the process from using that much memory during Application_Start, I'd like to at least be able to make it give up the memory when app start completes.

Tim Goodman
  • 23,308
  • 7
  • 64
  • 83
  • What ahappens if you do a *single* forced full garbage collect at the end of Application_Start? –  Jan 06 '12 at 20:35
  • A full garbage collection at the end of Application_Start didn't make a difference. The issue was that even after the large number of short lived objects were collected, all the memory they *had* been taking up was still allocated to the process. – Tim Goodman Mar 08 '13 at 16:16
  • However, I no longer believe this is a problem. Either the memory is eventually released by the process, or if total memory starts to fill up before that happens, then it can be swapped to disk. This question addresses a similar concern: http://stackoverflow.com/questions/5244980/when-is-memory-allocated-by-net-process-released-back-to-windows – Tim Goodman Mar 08 '13 at 16:19

1 Answers1

3

Gen 0 contains the "the youngest, most recently allocated objects", and is separate and distinct from the LOH. What it sounds like is that you are allocating tons and tons of small objects (all of the errata associated with those cache entries, by the sound of it) which are unrooted (demonstrated by the frequent GC's keeping the size down) but not cleaned up in a timely manner because the GC hasn't deemed it necessary yet. The GC simply has not yet seen a need to clean up. How much RAM does the machine have? I am guessing that you are not paging.

My understanding is that .NET can return chunks of unused heap to the OS when the GC deems it is no longer needed (because that heap space hasn't been used for a long time, for example). Have you observed the app over a long period of time, to see whether this happens? If you are not paging, I don't think it is a problem.

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • Thanks, Chris. I had thought that something like this might be the case (the garbage collector waiting a while to clean up small objects from previous iterations of the loop), but what I'd read was that when Gen 0 gets filled that triggers a collection. Apparently the GC can opt to increase the size of Gen 0 instead of collecting it; is that true? – Tim Goodman Jan 06 '12 at 20:54
  • That's true. It's a simple thought exercise- it would be impossible at startup time for the CLR to pre-allocate an appropriate heap size for your application, so the only possibility is that it starts very small (for "Hello world") and allocates additional space as needed. – Chris Shain Jan 06 '12 at 20:56
  • The test box I'm running the profiler on has plenty of free memory; what really prompted the investigation is that our app pools on the live servers are hitting their memory threshold and recycling more often than we'd like. (Much of this memory consumption happens later, but trying to reduce the seemingly-excessive memory usage during application start seemed a good place to begin.) But we have occasionally observed large drops in memory usage; maybe that's the GC returning a chunk of unused heap as you say. I suppose I just have to wait for the GC to decide to do this; I can't prompt it? – Tim Goodman Jan 06 '12 at 20:57
  • Not that I know of, but guys like Eric Lippert have been known to answer questions like this, I am sure he'd be able to say for sure. In your production environment it sounds like you may have a memory leak- I'd try reproducing live traffic loads in QA and profiling the memory usage when you can reproduce the problem. I'm fond of the saying "you cant fix what you can't measure/reproduce". – Chris Shain Jan 06 '12 at 21:00
  • Chirs, would you have any thoughts as to why in a situation like this the Gen0 space would never be collected? My SO question: http://stackoverflow.com/questions/29216659/iis-worker-process-accumulating-large-amounts-of-unused-memory –  Mar 23 '15 at 23:45