我正在尝试在更大的C#程序中追踪内存泄漏,该程序会生成多个线程。在这个过程中,我创建了一个小方程序,我用它来测试一些基本的东西,我发现了一些我真的不明白的行为。
class Program
{
static void test()
{
}
static void Main(string[] args)
{
while (true)
{
Thread test_thread = new Thread(() => test());
test_thread.Start();
Thread.Sleep(20);
}
}
}
运行这个程序,我看到程序的内存使用量稳定增加而不停止。在短短几分钟内,内存使用量超过100MB并继续攀升。如果我注释掉test_thread.Start();行,那么程序使用的内存最大可以达到几兆字节,并且会升级。我还尝试使用GC.Collect()在while循环结束时强制进行垃圾收集,但它似乎没有做任何事情。
我认为一旦函数完成执行就会解除引用线程,允许GC将其拖尾,但这似乎并没有发生。我不能在这里深入理解更深层次的东西,我将非常感谢帮助解决这个问题。提前谢谢!
答案 0 :(得分:11)
这是设计使然,您的测试程序应该显示内存使用失控。您可以从Taskmgr.exe中查看基本原因。使用View + Select Columns并勾选" Handles"。观察您的过程的句柄数量是如何稳步增加的。内存使用量随之增加,反映了句柄对象使用的非托管内存。
设计选择非常勇敢,CLR每个线程使用5个操作系统对象。管道,用于同步。这些对象本身是一次性的,设计选择是不使Thread类实现IDisposable。这对.NET程序员来说非常困难,很难在正确的时间调用Dispose()。在任务类设计中没有表现出勇气,导致大量的烦恼和general advice not to bother。
通常是设计良好的.NET程序中的问题。 GC运行的频率足以清理那些OS对象。并且线程对象正在谨慎创建,使用ThreadPool进行非常短的运行线程,就像您的测试程序一样。
可能,我们无法看到您的真实节目。请注意从这样的综合测试中得出太多结论。您可以使用Perfmon.exe查看GC统计信息,让您了解它是否经常运行。一个体面的.NET内存分析器是首选的武器。 GC.Collect()是备用武器。例如:
static void Main(string[] args) {
int cnt = 0;
while (true) {
Thread test_thread = new Thread(() => test());
test_thread.Start();
if (++cnt % 256 == 0) GC.Collect();
Thread.Sleep(20);
}
}
而且你会看到它现在来回反弹,永远不会超过4 MB。