关于.NET垃圾收集器的疑问

时间:2010-04-08 11:32:11

标签: c# garbage-collection

我已经阅读了一些关于.NET垃圾收集器的文档,但我仍有一些疑问(C#中的例子):

1)GC.Collect()调用部分或完整集合吗? 2)部分收集是否会阻止“受害者”应用程序的执行?如果是..那么我想这是一个非常“轻松”的事情要做,因为我正在运行一个使用2-3GB内存的游戏服务器而且我“从不”执行停止(或者我看不到它们...... )。 3)我已经阅读了关于GC根的内容,但仍然无法理解它们是如何工作的。假设这是代码(C#):

MyClass1的:

[...] 
public List<MyClass2> classList = new List<MyClass2>(); 
[...]

主:

main()
    {
     MyClass1 a = new MyClass1();
     MyClass2 b = new MyClass2();
     a.classList.Add(b);

     b = null;

     DoSomeLongWork();
    }

是否有资格被垃圾收集(在DoSomeLongWork完成之前)?对classList包含的b的引用,是否可以将其视为根? 或者root只是对实例的第一个引用? (我的意思是,b是根参考,因为实例化发生在那里)。

5 个答案:

答案 0 :(得分:5)

  1. GC.Collect有几个重载。没有任何参数的那个完全收集。但请记住,明确调用GC几乎不是一个好主意。

  2. 垃圾收集发生在托管应用程序中。 GC可能必须挂起该进程中的所有托管线程以压缩堆。如果这就是您所要求的,它不会影响系统上的其他进程。对于大多数应用程序而言,这通常不是问题,但由于频繁收集,您可能会遇到与性能相关的问题。您可以使用几个相关的性能计数器(GC中的时间百分比等)来监控GC的执行情况。

  3. 将root视为有效引用。在您的示例中,如果仍有对它的引用,则不会收集MyClass2的实例。即如果a指向的实例仍然是root,那么你的MyClass2实例也是如此。另外,请记住,GC在发布模式版本中比调试版本更具攻击性。

答案 1 :(得分:3)

  1. GC.Collect()执行完整集合。这就是为什么自己调用它不是一个好主意,因为它可以过早地推动一代人的对象

  2. AFAIK,不是真的。 GC阻止这么短的时间,以至于无关紧要。

  3. 堆栈GC根目录是当前由堆栈上的方法引用的对象。请注意,调试和发布版本之间的行为是不同的;在调试构建中,方法中的所有变量都保持活动状态,直到方法返回(因此您可以在调试窗口中看到它们的值)。在发布版本中,CLR会跟踪方法变量的使用位置,GC可以删除当前正在执行的方法引用的对象,但不会在仍要执行的方法部分中使用。

  4. 因此,在您的示例中,a调用后bDoSomeLongWork()都未被引用,因此在发布版本中,在该方法执行期间,它们都有资格收集。在调试版本中,它们会一直存在,直到main()方法返回。

答案 2 :(得分:2)

垃圾收集是automatic。除非处理非托管资源,否则不必干涉。只要对象超出范围,就会收集垃圾;垃圾收集器认为必要时以特定的间隔 - 例如,操作系统需要内存。这意味着无法保证多久,但在回收这些对象的内存之前,您的应用程序不会耗尽内存。

  

是否有资格被垃圾收集(在DoSomeLongWork完成之前)?

是的,每当垃圾收集者发现必要时。

结帐Garbage Collector Basics and Performance Hints

答案 3 :(得分:1)

  1. 是的,GC.Collect()执行完整收集,但您可以手动GC.Collect(0)来执行gen 0。

  2. 所有集合都会阻止线程,但部分集合只会非常简短。

  3. 不,MyClass2的实例仍可在其他列表中访问。 ab都是根引用(在DoSomeLongWork期间),但由于b为空,无关紧要。请注意,GC与引用类型的概念密切相关。 b只是引用匿名对象的本地var。 GC的“根”是静态字段,堆栈中的所有内容,甚至是CPU寄存器。以更简单的方式:您的代码仍然可以访问的所有内容。

答案 4 :(得分:1)

  

是否有资格被垃圾收集(在DoSomeLongWork完成之前)?

潜在的是,如果ab被编译器驱逐出全局根集(即没有保留在堆栈中),那么它们可以在{{1}期间被集合回收}。

我发现.NET很快就回收了Mono leaks memory

  

对classList包含的b的引用,是否可以视为root?

DoSomeLongWork是否会变成全局根完全取决于编译器。

  • 玩具编译器可能会从函数参数中推送引用,并从函数调用返回到堆栈,并在每个函数的末尾展开堆栈。在这种情况下,b将被推入堆栈,因此将成为全局根。

  • 生产质量编译器执行复杂的寄存器分配,并仅在堆栈上维护实时引用,或者在堆栈丢失时覆盖或归零堆栈上的引用。在这种情况下,b在调用b期间已经死亡,因此其堆栈条目将被取消或覆盖。

这些都不能从源代码中推断出来,也没有关于编译器究竟会做什么的细节。例如,我的HLVM项目在此阶段只是一个玩具,使用前一种技术,但在这种情况下实际上会收集DoSomeLongWorka,因为调用{{1}是一个尾调用。

  

或者root只是对实例的第一个引用?

全局根是GC开始的引用,以遍历堆中的所有可访问数据。全局根通常是全局变量和线程堆栈,但更复杂的GC算法可以引入新的全局根,例如记得集。