C# GC会清理非活动但在范围内引用的对象吗?

C# GC会清理非活动但在范围内引用的对象吗?,c#,.net,garbage-collection,C#,.net,Garbage Collection,我想知道在这种情况下,我是否可以依靠.NET垃圾收集器来避免保留一堆额外的堆对象: publicstaticvoidmain(字符串[]args){ var a=ParseFileAndProduceABigTreeObject(args[0]); var b=遍历BigtreeObject并生成其他BigObject(a); var c=第二轮处理(b); Console.WriteLine(c.ToString()); } 在这里的每个阶段,应该理解的是,每个方法返回的对象不包含对前面对象

我想知道在这种情况下,我是否可以依靠.NET垃圾收集器来避免保留一堆额外的堆对象:

publicstaticvoidmain(字符串[]args){
var a=ParseFileAndProduceABigTreeObject(args[0]);
var b=遍历BigtreeObject并生成其他BigObject(a);
var c=第二轮处理(b);
Console.WriteLine(c.ToString());
}
在这里的每个阶段,应该理解的是,每个方法返回的对象不包含对前面对象的引用,因此
b
不引用
a
c
不引用
a
b

一个简单的GC实现将在整个程序期间保持
A
,因为它仍然可以通过
Main
方法的堆栈框架访问

我想知道的是,我是否可以依靠.NET进行活动性分析,并确定在执行第三行(
a第三轮处理
)时,
a
不再需要,并且如果需要,可以回收其内存

我几乎可以肯定,.NET至少有时会处理类似的情况,所以我的问题是:它是否足够一致,我可以依赖它,还是我应该假设它可能不会,并采取措施使代码更简单?(例如,在这里,我可以将
a
设置为
null

旁白:OpenJDK怎么样,它能很好地处理它吗

编辑:我可能不清楚。我知道,就标准而言,允许但不要求运行时收集
a
。如果我在一个假想的运行时运行我的代码,我只知道它是一致的,我将不得不忍受这种不确定性。但是,我的代码正在Microsoft.NET 4运行时的最新版本上运行。我想知道的是,运行时是否可以实现这一点。运行时是实际的代码,可以确切地知道它会做什么。也许有人有这样的知识并愿意分享。

你永远不能依靠GC来清理任何东西。只要符合收集条件,就不需要清理对象。整个
IDisposable
模式的存在正是因为没有确定的方式让GC清理资源。GC的强大之处在于它不必在资源的生命周期结束时立即清理资源。它能够更有效地完成工作,因为它可以随时自由地清理符合条件的资源,而不需要对何时需要清理给定的资源有任何要求


只要运行时能够证明该对象以后再也不能从执行的代码中访问,该对象就有资格被收集,因此在您的情况下,根据您的描述,这些对象有资格被收集,但是,您完全不可能期望在整个过程被破坏之前的任何时候都会收集到这些数据。

您似乎只对测试特定版本的.NET感兴趣。下面是一个快速示例程序,可以测试运行时在运行特定代码的特定配置中对特定代码执行的操作

static void Main(string[] args)
{

    var a = ParseFileAndProduceABigTreeObject(args[0]);
    var aWeakReference = new WeakReference(a);

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    Console.WriteLine($"a: {aWeakReference.IsAlive}");

    var b = WalkTheBigTreeObjectAndProduceSomeOtherBigObject(a);
    var bWeakReference = new WeakReference(b);

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    Console.WriteLine($"a: {aWeakReference.IsAlive} b: {bWeakReference.IsAlive}");

    var c = AThirdRoundOfProcessing(b);
    var cWeakReference = new WeakReference(c);

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    Console.WriteLine($"a: {aWeakReference.IsAlive} b: {bWeakReference.IsAlive} c:{cWeakReference.IsAlive}");

    Console.WriteLine(c.ToString());

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    Console.WriteLine($"a: {aWeakReference.IsAlive} b: {bWeakReference.IsAlive} c:{cWeakReference.IsAlive}" );

    Console.ReadLine();
}
在调试模式下,无论是否使用调试器

a: True
a: True b: True
a: True b: True c:True
This is some processed Result!
a: True b: True c:True
在发布模式下,在4.5.2中,无论是否连接调试器,您都会得到

a: False
a: False b: True
a: False b: False c:True
This is some processed Result!
a: False b: False c:False

虽然我不相信带有调试器附加结果的版本,但我真的希望得到与调试版本相同的结果,我可能只是设置不可靠。

其他答案和评论已经解释说,不保证收集。你似乎明白这一点

实际上,这是可行的。JIT跟踪本地值生命周期,这是一个相当容易的跟踪问题

出于向后兼容性的原因,JIT很难进行不太精确的跟踪,因为这可能会导致一些应用程序的内存使用激增。所以随着时间的推移,跟踪不太可能失去精度是常见的情况

我相信框架代码也依赖于此。我见过图书馆依赖它,我自己也依赖它

显然,这不是一个参考答案,但这是“常识”,这将起作用

请注意,在调试模式下,局部变量的生存时间会延长到方法调用的末尾,以帮助调试。因此,这需要优化JIT操作

如果您想更加确保收集将发生,您可以尝试分离方法。分离堆栈外帧更可靠,但仍然不能保证。另一个类似的想法是将局部变量放入
对象[]
,并在处理完该对象后在该数组中显式地设置空槽。同样,也不能保证

您提到
a=null作为另一种策略。这在调试模式下可能会有所帮助。在优化模式下,JIT将终止对死变量的赋值。这只会在接近JIT bug的病理情况下有所帮助。这不是一个好策略

没有任何收集的保证,因为null垃圾收集满足运行时做出的所有保证


虽然在这种情况下没有正式的保证,但程序员始终依赖于隐式保证。例如,许多应用程序依赖于
可枚举。选择
而不是重新排序元素。这在专业中是不能保证的,但是大多数程序员相信这种行为会感到舒服


在所有情况下,仅仅依靠正式保证的行为并不是一种有用的态度。除非在为火星漫游者或Therac 25(一种医疗设备,将病人照射致死)编程时。

如果您担心它会使引用保持活动状态,直到超出范围,您难道不能将其称为第二轮处理(WalktheBigtreeObject并生成其他BigOb)吗