In my job we had a problem with OutOfMemoryExceptions. I've written a simple piece of code to mimic some behavior, and I've ended up with the following mystery. Look at this simple code which blows up when it runs out of memory.
class Program { private static void Main() { List<byte[]> list = new List<byte[]>(200000); int iter = 0; try { for (;;iter++) { list.Add(new byte[10000]); } } catch (OutOfMemoryException) { Console.WriteLine("Iterations: " + iter); } } }
On my machine it ended up with
Iterations: 148008
Then I added a GC.Collect
call to the loop after each thousand iterations:
//... for (;;iter++) { list.Add(new byte[10000]); if (iter % 1000 == 0) GC.Collect(); } //...
And surprise:
Iterations: 172048
When I called GC.Collect
after each 10 iterations, I even got 193716 cycles. There are two strange things:
How can a manual call to GC.Collect
have such a severe impact (up to 30% more allocated)?
What the hell can GC collect, when there're no "lost" references (I've even preset the List's capacity)?
. NET's garbage collector manages the allocation and release of memory for your application. Each time you create a new object, the common language runtime allocates memory for the object from the managed heap.
The garbage collector (GC) manages the allocation and release of memory. The garbage collector serves as an automatic memory manager. When there isn't enough memory to allocate an object, the GC must collect and dispose of garbage memory to make memory available for new allocations.
Garbage collection occurs when the system is low on available physical memory or the GC. Collect() method is called explicitly in your application code. Objects that are no longer in use or are inaccessible from the root are candidates for garbage collection.
The garbage collector serves as an automatic memory manager. You do not need to know how to allocate and release memory or manage the lifetime of the objects that use that memory. An allocation is made any time you declare an object with a “new” keyword or a value type is boxed. Allocations are typically very fast.
A part of the garbage collection process is the compacting phase. During this phase, blocks of allocated memory are moved around to reduce fragementation. When memory is allocated, it isn't always allocated right after the last chunk of allocated memory left off. So you are able to squeeze a bit more in because the garbage collector is making more room by making better use of the available space.
I am trying to run some tests, but my machine can't handle them. Give this a try, it will tell the GC
to pin down the objects in memory so they aren't moved around
byte[] b = new byte[10000]; GCHandle.Alloc(b, GCHandleType.Pinned); list.Add(b);
As for your comment, when the GC
moves things around, it isn't wiping anything out, it is just making better use of all memory space. Lets try and over simplify this. When you allocate your byte array the first time, lets say it gets inserted in memory from spot 0 to 10000. The next time you allocate the byte array, it isn't guarenteed to start at 10001, it may start at 10500. So now you have 499 bytes that aren't being used, and won't be used by your application. So when the GC
does compacting, it will move the 10500 array to 10001 to be able to use that extra 499 bytes. And again, this is way over simplified.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With