Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding .NET GC and OutOfMemory Exceptions

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

like image 274
HH. Avatar asked Dec 22 '22 09:12

HH.


2 Answers

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).
like image 50
Joel Coehoorn Avatar answered Dec 24 '22 23:12

Joel Coehoorn


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.

like image 30
Brian Rasmussen Avatar answered Dec 24 '22 21:12

Brian Rasmussen