问题
I was learning more about threading, and i created a rather simple WPF application with the following code (x64 platform build)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (var i = 0; i <= 20000; i++)
{
Thread thread = new Thread(Test);
thread.IsBackground = true;
thread.Start();
}
}
public void Test()
{
Thread.Sleep(20000);
}
}
When i run this code, process takes approximately 560MB of RAM while all threads are running / sleeping.
Upon it's completion, process usage is down to aprox 125 MB of RAM.
My question is, why does process use 125 MB of RAM at that point, when application itself (without thread example) is using only 30 MB of RAM?
Does it keep some of the threads alive for possible re/usage or something else is going on?
EDIT:
Due to some suggestions how to improve this code, I would like to point out that I am not asking a way to improve it, but to pinpoint the reason for this behavior.
EDIT 2:
This is not a thread related, but I have tried a case with a large string
list in memory, and it did not produce same results. When list was fully loaded in memory, it took about 1.3 GB of memory, but after list was set to NULL
, and GC.Collect()
was called, memory usage dropped back to 30 MB as expected.
Code:
public partial class MainWindow : Window
{
List<string> stringArray = new List<string>();
public MainWindow()
{
InitializeComponent();
for (var i = 0; i <= 100000000; i++)
{
//Thread thread = new Thread(Test);
//thread.IsBackground = false;
//thread.Start();
stringArray.Add("Some very long string to pump up the memory volume 2 reloaded");
}
stringArray = null;
GC.Collect();
}
}
回答1:
Part of the memory used by each thread is deallocated when the thread completes. That's why the memory consumption drops from 560 MB to 125 MB when all background threads complete.
The rest of the memory is allocated on the managed heap and will be freed by garbage collector.
When your application sits idle, no new memory is allocated, so there is no need for the garbage collector to run. You mentioned in a comment that forcing GC using GC.Collect()
didn't help, but keep in mind that Thread
has a finalizer implemented, so it will survive the first collection. You will have to use
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
to make sure the memory is freed.
That should reduce the memory usage to the level before starting the threads.
回答2:
You are running a 64 bits application that just creates a lot of threads. First, it is important to know that running a Debug build of an application can yield different results than running a Release build because variables are artificially kept longer than needed in memory. Thus they will not be collected as soon as possible.
The Garbage Collector is highly optimized and will be triggered when:
- The Gen0 has been fully allocated (the size of Gen0 is based on heuristics such as the rate at which your application do allocations etc);
- The available memory is running out;
- GC.Collect is called.
The reason the GC does not collect the memory as quickly as you thought is because there are simply no reason for the GC to collect memory. If your application would be running on a 32 bits platform, it would still have 1.5GB of memory available. In your case, you are running a 64 bits application: there is plenty memory available. However, if you were running a 'real' application with a lot more memory allocations, you would have a different outcome and probably a lot more collections (and thus a smaller working set).
Finally, calling GC.Collect is often unnecessary and can mess up the GC's heuristics which can badly impact the performance of your application because GC.Collect will trigger the collection of all the generations.
https://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx
回答3:
Your threading mechanism is part of the problem. Besides it taking up so much cpu time during the execution (reducing the opportunity for garbage collection) it saw-tooth's your memory usage.
Something like this (minus the debugging information) will keep your memory consumption pretty even. And your CPU should be 'loafing' during the operation.(the static keyword is from my testing in a console application). .Net framework 4.0 or higher is required. If you are constrained to pervious versions of .net then I would suggest a slight pause before you start the new task to allow the garbage collection to do it's magic.
private static void ThreadStuff()
{
long startSet = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64;
List<long> endSet = new List<long>();
for (var i = 0; i <= 20000; i++)
{
Action Act = new Action(() => Test(i));
Task Tsk = new Task(Act);
Tsk.Start();
endSet.Add(System.Diagnostics.Process.GetCurrentProcess().WorkingSet64);
int worker;
int ioCompletion;
ThreadPool.GetMaxThreads(out worker, out ioCompletion);
Console.WriteLine(worker);
Console.WriteLine(ioCompletion);
}
Console.WriteLine(startSet.ToString("###,###,###,###,###,###.####"));
Console.WriteLine(endSet.Average().ToString("###,###,###,###,###,###.####"));
}
public static void Test(int Index)
{
Thread.Sleep(2000);
Console.WriteLine(Index.ToString() + " Done");
}
来源:https://stackoverflow.com/questions/33846838/c-sharp-thread-memory-usage