I'm seeing some very strange behavior with the GC under C#. I'm seeing it
in two different applications that have nothing in common except that they
both use and release a lot of memory in BYTE or CHAR arrays.
The first one is easier to describe, but the behavior is similar in both.
WinXP on a machine with 512Meg and NO swap file (for performance).
I'm using a winForms app as a testing harness against some networking code
that stores results in a MemoryStream. The results are processed, then all
objects are released. Watching system memory in Task Manager shows that
memory is getting eaten up, but never freed. Because there's no swap file,
Windows can't increase the memory, so the application will eventually crash
with an out-of-memory error. Interestingly enough, it will actually do this
even though it has plenty of memory waiting to be collected.
The MemoryStream class increases capacity as needed by allocating a new
buffer of double the existing size and copying the data to the new buffer.
I'm starting it at one 1MB, so it goes 1,2,3,4,8,16,.... When it gets to
the point where it needs to allocate a 256 meg block, it will fail with a
memory error because there is only about 200MB of system memory left. The
annoyance is that it actually has enough memory, if it would just collect
the old buffers. You see, at this point, the MemoryStream is using a 128MB
buffer, but has 127MB in discarded buffers (1+2+4+8+16+32+64). If I throw
in calls to GC.Collect(), it works fine.
I had first thought that the GC just wasn't collecting because I was using
all of the CPU, but it just never seems to collect. After a smaller run, I
left the testing harness up for 30 minutes after everything has been freed,
and it just doesn't collect, however, if I put a call to
GC.GetTotalMemory(true) on a button, it instantly frees all of the memory.
GC.GetTotalMemory() reports 83Meg used then GC.GetTotalMemory(true) shows
461K.
I have spread calls to GC.Collect through the code so solve the problem, but
it seems foolish to me. Any thoughts?
By the way, this happens inside and outside of VS as well as in code
compiled for Release. And, of course, I already have a call to
IDisposable.Dispose() on the MemoryStream.