循环中不可访问对象的垃圾收集
本文关键字:对象 访问 循环 | 更新日期: 2023-09-27 18:08:16
如果我有一个这样的循环:
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()时立即下降。
下面是稍微修改过的程序,更清楚地演示了这种行为:
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上,当我在调试模式下构建和目标任何CPU或x86时,我重现了您所看到的行为:直到"退出"打印之后实例才最终确定。但是,当我在发布模式或目标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优化,相同的寄存器或堆栈变量为每个实例重用,从而释放初始实例。