views:

460

answers:

2

I'm troubleshooting an OutOfMemory exception in my .NET 2.0 Windows Service application. To understand the issue better I began by writing a simple .NET WinForm test app that generates an OOM Exception by building an ArrayList until an OOM Exception is thrown. The exception is caught and logged and I can click on a form button to run the OOME again. The strange thing I found was on the 4th run, the amount of memory consumed before the next OOME was roughly half. The results listed below are consistent every time I run this. Eyeballing TaskManager also confirms the behavior. Unfortunately, Perfmon froze up when trying to get better stats. Can someone explain why the memory limit is lowered after 3 runs? My understanding of GC is fairly shallow. You can also see I ran a GC.Collect() after a few more runs but it didn't help with the lowered limit.

UPDATE: I also found a big difference using a const string vs a new object for each arraylist item. Code is simply:

const string TEST_TEXT = "xxxxxxxxxx";
ArrayList list = new ArrayList();
while (true)
{
    list.Add(TEST_TEXT);
}


Start Loop: memory 10,350,592

  • OOM Exception Thrown
  • Array Size: 134,217,728

End Loop: memory 550,408,192

Start Loop: memory 550,731,776

  • OOM Exception Thrown
  • Array Size: 134,217,728

End Loop: memory 551,682,048

Start Loop: memory 551,813,120

  • OOM Exception Thrown
  • Array Size: 134,217,728

End Loop: memory 551,772,160

Start Loop: memory 551,903,232

  • OOM Exception Thrown
  • Array Size: 67,108,864

End Loop: memory 282,869,760

Start Loop: memory 283,004,928

  • OOM Exception Thrown
  • Array Size: 67,108,864

End Loop: memory 282,910,720

GC.Collect manually triggered

Start Loop: memory 14,245,888

  • OOM Exception Thrown
  • Array Size: 67,108,864

End Loop: memory 283,344,896

+7  A: 

Here are several points which, taken together, hopefully will give you enough information to answer your question:

  • In spite of it's name, OutOfMemory exceptions are just as likely to mean you are out of Address Space as physical RAM.
  • GC.Collect does not collect all outstanding RAM. Garbage collections in .Net are non-deterministic, meaning there is no way to force the runtime to clean up all your RAM.
  • The garbage collector in .Net is generational, meaning when an object survives collection it moves up to a higher generation, making it even less likely to be collected.
  • By the time your OutOfMemory exception is thrown, your array has probably already survived a few collection attempts or was even moved to the LargeObjectHeap.
  • Array sizes are fixed. To add a new element to an array you must completely re-allocate the array. (You might get better test results using a structure like a list).
Joel Coehoorn
+1  A: 

Since you are building arrays, I assume that you are building one big array for each run. If this is the case it will be stored in the Large Object Heap (as it will be > 85000 bytes). LOH is not compacted like the generational heap, so the drop in size you're seeing is probably due to heap fragmentation.

Brian Rasmussen
Sorry - I am actually using an ArrayList. See code above.
HH
There's little reason to use ArrayList these days, but it doesn't really make a difference in this case, casuse ArrayList uses arrays internally as well.
Brian Rasmussen