I'm trying to get a better grasp of the .NET threading model. I've heard and read multiple times (most recently when watching this video: AppFabric.tv - Threading with Jeff Richter) that .NET threads take up at least 1MB of memory (because they set aside 1MB for their stack). Now, I tried to write some code to demonstrate this, but I end up with results like
297 threads are using 42MB of memory
298 threads are using 43MB of memory
299 threads are using 40MB of memory
300 threads are using 40MB of memory
So, the threads are seemingly not using 1MB of memory each. I have tried to the best of my ability to reproduce the program demonstrated 5 minutes into the aforementioned video, but I don't seem to get the same result and I don't understand why. As the memory consumption seems to be falling at times, I guess threads must be exiting or be put on a back burner somewhere? Or maybe I'm not measuring memory properly?
The program used to get the results above looks like this:
class Program
{
static void Main(string[] args)
{
ManualResetEvent manualReset = new ManualResetEvent(false);
int createdThreads = 0;
try
{
while (true)
{
Thread t = new Thread(WaitOneEvent);
t.Start(manualReset);
createdThreads++;
Console.WriteLine("{0} threads are using {1}MB of memory", createdThreads, System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / (1024 * 1024));
}
}
catch (OutOfMemoryException)
{
Console.WriteLine("Out of memory at {0} threads", createdThreads);
System.Diagnostics.Debugger.Break();
manualReset.Set();
}
}
private static void WaitOneEvent(object eventObject) {
((ManualResetEvent)eventObject).WaitOne();
}
}
Any insight would be greatly appreciated.
You might like to use VirtualMemorySize64()
but PrivateMemorySize64()
.
MS writes here:
Each new thread or fiber receives its own stack space consisting of both reserved and initially committed memory. The reserved memory size represents the total stack allocation in virtual memory. [...] The initially committed pages do not utilize physical memory until they are referenced; [...]
As the threads in the OP's example code do nothing else but waiting to be signalled, their (stack) memory does not use physical memory and therefore will not be taken into account by PrivateMemorySize64()
.
Your measurement is simply too crude to accurately trace the thread stack memory consumption. It measures the amount of virtual memory that cannot be shared with another process. The only sharable memory in a .NET process is code, the native Windows DLLs, the CLR, the jitter and any native images generated by ngen.exe (normally only the .NET framework assemblies).
Everything else is private memory. Jitted code, loader heap and the garbage collected heaps are the main chunks. Which is what is throwing it off, the garbage collector will shrink virtual memory allocation when it can.
You can get better insight in the virtual memory usage with SysInternals' VMMap utility. You'll drown in the details but it shows thread stacks as a separate category.
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