Sorry that it has been a while since my last Blog. I moved desks and got pulled in to a new project that meant some very late nights indeed.
Now, what did I lie about? I lied about heap fragmentation. It was more of a lie by omission.
With ordinary managed heap, code such as that in my last blog will not really fragment the heap (but see later). It will just cause lots and lots of generation 0 entries. The net effect of this from the point of view of your application is pretty much identical. The memory grows and grows and the time taken in memory management soars.
The reason that I lied was that what I described was a conventional heap like a classic C++ heap – or the large object heap. Objects over 85K in
Fragmentation of the managed heap is mainly caused by pinned objects – that is to say, those fixed in memory. These are used when calling in to unmanaged code. Managed code doesn’t mind if things move around during a GC. The reason is that GCs can only happen when all threads in a process are in a state where it is safe to GC. They will have direct memory references, sometimes in registers, but that is fine because the GC will fix up the reference and the threads will carry on where they left off and never be any the wiser. The GC knows when it can run because there are a number of GC safe points built in by the JITer. The GC can let a code run to a safe point and suspend it when required.
So, that explains why managed code survives the memory moving around. What about unmanaged code? Well, that doesn’t have safe points because it doesn’t know a garbage collector from a hole in the ground. We don’t pause unmanaged code when we do a GC. We let it run on but we perform a little bit of smoke and mirrors. A call to unmanaged code doesn’t come from your code but from a special routine in the CLR. It calls the code and gets the return. If there is no GC going on when the unmanaged code returns then execution returns to your managed code and you don’t care about the shim. If there is a GC going on, the shim routine blocks until the GC has finished and the memory has stopped moving around. It is quite transparent.
Because “It just works”, a common error is to pass a managed block of memory to an unmanaged routine (such as an asynchronous read) that will fill in the memory for you at some future time. The GC will fix up any references in managed code but has no idea what unmanaged code is doing. It wasn’t told not to move the memory and so it does. The unmanaged code does what it was told and puts the data in to the location where it was told to put it. What is there now? Your guess is as good as mine. Generally, this will corrupt the managed heap and we will discover it when we next do a GC. This is called a GC Hole. Any time that you see the GC crash, it is always a wise bet to suspect calls to PInvoke.
Sorry if my original entry was misleading. I should have followed some advise that one of my lecturers was fond of: A thing should be made as simple as possible but no simpler.
My next blog really will be on remote debugging