如果我有这样的循环:
public class Foo {
public Foo Foo;
public Foo() {
}
}
class Program {
public static void Main(string[] args) {
var foo = new Foo();
long i = 0;
while(i < Int64.MaxValue) {
foo.Foo = new Foo();
foo = foo.Foo;
if(i % 10000 == 0)
GC.Collect();
i++;
}
GC.Collect();
}
}
在退出循环之前,垃圾收集器不会清理父对象。这是为什么?在foo
重新分配后,我没有看到任何方法从代码中引用它们,所以不应该清除它们吗?
在传递了我设置的一些断点以确定发生这种情况后,我在任务管理器中查看了该进程的内存使用情况。它继续在循环中上升(如果我使其无限,则高达数GB),但在循环退出并且第二个GC.Collect()被调用时立即下降。
答案 0 :(得分:6)
这是一个稍微修改过的程序,可以更清楚地演示行为:
class Foo
{
public int Value;
public Foo Next;
public Foo(int value) { this.Value = value; Console.WriteLine("Created " + this.Value); }
~Foo() { Console.WriteLine("Finalized " + this.Value); }
}
class Program
{
public static void Main(string[] args)
{
var foo = new Foo(0);
for (int value = 1; value < 50; ++value)
{
foo.Next = new Foo(value);
foo = foo.Next;
if (value % 10 == 0)
{
Console.WriteLine("Collecting...");
GC.Collect();
Thread.Sleep(10);
}
}
Console.WriteLine("Exiting");
}
}
在.NET 4.5上,当我在调试模式下构建 AND 目标任何CPU或x86时,我重现你所看到的行为:直到打印出“退出”之后才终止实例。但是当我在发布模式下构建 OR 目标x64时(即使在调试模式下构建),实例一旦无法访问就会最终确定:
Created 0
Created 1
Created 2
Created 3
Created 4
Created 5
Created 6
Created 7
Created 8
Created 9
Created 10
Collecting...
Finalized 9
Finalized 0
Finalized 8
Finalized 7
Finalized 6
Finalized 5
Finalized 4
Finalized 3
Finalized 2
Finalized 1
Created 11
Created 12
Created 13
...
为什么会这样?我想只有一位CLR专家可以肯定地告诉我们,但这是我的猜测:行为取决于JIT编译器和优化器碰巧生成的机器代码的具体细节,细节因目标指令集而异,以及是否“在调试模式下重新运行。 (此外,这些细节可能会在运行时的未来版本中发生变化。)特别是在x86 / Debug的情况下,我认为第一个Foo(0)
实例被隐藏在永远不会得到的寄存器或堆栈变量中在方法的其余部分覆盖;这个初始实例使整个链保持活力。在x86 / Release和x64的情况下,我认为由于JIT优化,相同的寄存器或堆栈变量被重用于每个实例,从而释放初始实例。