GC::PleaseRunWhenProcessRunningOutOfMemory()

I don’t think anybody except the guys who coded the GC knows the list of triggers for garbage collection. I believe it’s not part of the CLR spec and I guess that was a deliberate decision to allow implementors flexibility in their GC algorithms. That said, there are some situations which leave one confounded as to why the heck the GC doesn’t run. Take a look at the following program.

#include “stdafx.h”
#include “windows.h”

using namespace System;

ref class Allocator
{
	LPVOID ptr;
public:
	Allocator()
	{
		ptr = VirtualAlloc(NULL, 1024 * 1024 * 1024, MEM_RESERVE, PAGE_EXECUTE_READWRITE);
		
		if (ptr == NULL)
			Console::WriteLine(“Virtual Alloc failed”);
		else
			Console::WriteLine(“Allocated unmanaged memory”);
	}

	!Allocator()
	{
		VirtualFree(ptr, 0, MEM_RELEASE);
	}
};

int main(array<System::String ^> ^args)
{
	array<byte> ^bigArray = gcnew array<byte>(1024 * 1024 * 1024);
	Allocator ^allocator = gcnew Allocator();
}

Allocator reservers 1 GB of virtual memory in its constructor and releases it in its finalizer. The main method instantiates a big managed byte array with 1 GB elements and then instantiates an instance of Allocator.


Without the notion of GC, this is obviously going to fail as Windows limits user address space in a process to around 1.5 GB. With GC, however, I would expect this to run, because bigArray can be GC’ed before executing VirtualAlloc (on Release builds).


Unfortunately it doesn’t. The GC doesn’t seem to care about unmanaged memory allocations and therefore sits idle when VirtualAlloc runs – this causes VirtualAlloc to fail. Insert a GC::Collect between the two lines in main and it all runs fine.


I ran into a variant of this issue the other day and was left with no option but to force GC explicitly using GC::Collect. I later read about GC::AddMemoryPressure and GC::RemoveMemoryPressure and those work perfect for these scenarios. Just put a AddMemoryPressure call before allocating a big chunk of unmanaged memory (with the corresponding RemoveMemoryPressure) and the GC kicks in.

	Allocator()
	{
                  GC::AddMemoryPressure(1024 * 1024 * 1024);
		ptr = VirtualAlloc(NULL, 1024 * 1024 * 1024, MEM_RESERVE, PAGE_EXECUTE_READWRITE);
		
		if (ptr == NULL)
		   Console::WriteLine(“Virtual Alloc failed”);
		else
		   Console::WriteLine(“Allocated unmanaged memory”);
	}

	!Allocator()
	{
		VirtualFree(ptr, 0, MEM_RELEASE);
                  GC::AddRemovePressure(1024 * 1024 * 1024);
	}


It didn’t help in my case though, as the unmanaged allocations were occuring in a third party component we had no control over.