GC会清理非实时但范围内引用引用的对象吗?

时间:2016-04-08 19:53:15

标签: c# .net garbage-collection

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

public static void Main(string[] args) {
    var a = ParseFileAndProduceABigTreeObject(args[0]);
    var b = WalkTheBigTreeObjectAndProduceSomeOtherBigObject(a);
    var c = AThirdRoundOfProcessing(b);
    Console.WriteLine(c.ToString());
}

在这里的每个阶段,应该理解每个方法返回的对象不包含对先前对象的引用,因此b不引用a,而c不会不参考ab

GC的简单实现会在程序的整个持续时间内保持a,因为它可以通过Main方法的堆栈帧继续访问。

我想知道的是,如果我可以依赖.NET进行活体分析,并确定在第三行(AThirdRoundOfProcessing)执行时,不再需要a如有必要可以回收记忆?

我几乎可以肯定.NET至少有时会处理这样的情况,所以我的问题就是这样:它是否足以让我依赖它,或者我应该假设它可能不会并采取措施使代码更加万无一失? (例如,我可以将a设置为null。)

P.S。:OpenJDK怎么样,它会很好地处理它吗?

编辑:我可能不太清楚。我知道,就标准而言,允许运行时但不需要收集a。如果我在一个想象中的运行时运行我的代码,我所知道的是它符合它,我将不得不忍受这种不确定性。但是,我的代码在最新版本的Microsoft .NET 4运行时上运行。我想知道的是,运行时是否可以预期这样做。运行时是实际的代码,可以准确地知道它会做什么。也许那里有人有这方面的知识,并愿意分享。

3 个答案:

答案 0 :(得分:4)

你永远不能依赖在GC上清理任何东西。一旦他们有资格收集对象,它就永远不会要求清理对象。整个IDisposable模式的存在恰恰是因为GC没有确定性的方法来清理资源。 GC的强大之处在于,不会在其生命周期结束后立即清理资源。它能够更有效地完成工作,因为它可以随时自由地清理符合条件的资源,而几乎没有任何关于何时需要清理给定资源的要求资源。

一旦运行时可以证明永远不会再从以后执行的代码中访问该对象,该对象就可以符合的集合,因此在您的情况下,根据您的描述,这些对象符合条件的用于收集,但在整个过程被拆除之前,您无法预期在任何时候实际收集它们。

答案 1 :(得分:2)

您似乎只对测试特定版本的.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

我不相信调试器附加结果的发布,我真的期望与调试版本相同的结果,我可能只是让我的设置不稳定。

答案 2 :(得分:2)

其他答案和评论已经解释说无法保证收集。你好像明白了。

在实践中,这将起作用。 JIT跟踪本地值的生命周期,这是一个相当容易的跟踪问题。

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

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

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

注意,在调试模式下,局部变量的生命周期会延长到方法调用的末尾,以帮助调试。所以这需要优化的JIT操作。

如果您想更加确定收集将会发生,您可以尝试关闭方法。分离堆栈帧更可靠但仍无法保证。另一个类似的想法是当你完成该对象时,将locals放入object[]并在该数组中显式为null。再一次,不能保证。

您提到a = null;作为另一种策略。这可能有助于调试模式。在优化模式下,JIT会将该赋值杀死为死变量。这只会对与JIT错误相关的病态情况有所帮助。不是一个好策略。

无法保证任何集合,因为空垃圾收集满足运行时所做的所有保证。

虽然在这种情况下确实没有正式的保证,但程序员始终依赖隐式保证。例如,许多应用依赖Enumerable.Select不重新排序元素。 documentation并不能保证这一点,但大多数专业程序员都会觉得依赖这种行为会很舒服。

在所有情况下,仅依靠正式保证的行为并不是一种有用的态度。除了编程火星探测器或Therac 25(一个照射患者致死的内侧设备)时。