What reasons cause the .NET runtime to throw an OutOfMemoryException? The garbage collector's job is to clean up memory and free memory as necessary before allocating objects; why would it appear to be out of memory?
I find this blog post by Eric Lippert to be rather informative. One thing that is mentioned in it is (simplified) that out of memory exceptions are often related to the fact that the process can not obtain a block of memory that is large enough for its current needs. There may very well be memory available, but fragmented in pieces that are too small to be of use.
If you use more memory than you have and don't free it... you'll end up out of memory. GC cleans only stuff that you do not reference anymore.
The garbage collectors job is much more complicated than just sweeping up after your objects. The gc must also be aware of how you are allocating memory, ensure that adequate size chunks are available for all the different parts of your application (think about multiple threads, etc).
the garbage collector also must be responsible for reliably cleaning up memory and doing it in a way that does not significantly affect performance, as well as ensuring that it does not introduce even more "problems" into your code when it tries to decide what memory can be reclaimed and what cant.
Its a big job, and one that is not easy or straightforward to implement.
To your specific point, OOM can happen for a variety of reasons, but in a reasonably well behaved application (i.e. doesnt allocate unreasonable sized objects, etc) it typically occurs because the memory requests occur more frequently than the GC can reasonable adjust the available memory to fulfill it, and the GC cannot adequately reassambly fragmented chunks fast enough to not stall out. In this case the GC is forced to report and OOM to the application to prevent more requests from backlogging and ensuring that it can never catch up.