我想知道我是否可以依赖.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
不会不参考a
或b
。
GC的简单实现会在程序的整个持续时间内保持a
,因为它可以通过Main
方法的堆栈帧继续访问。
我想知道的是,如果我可以依赖.NET进行活体分析,并确定在第三行(AThirdRoundOfProcessing
)执行时,不再需要a
如有必要可以回收记忆?
我几乎可以肯定.NET至少有时会处理这样的情况,所以我的问题就是这样:它是否足以让我依赖它,或者我应该假设它可能不会并采取措施使代码更加万无一失? (例如,我可以将a
设置为null
。)
P.S。:OpenJDK怎么样,它会很好地处理它吗?
编辑:我可能不太清楚。我知道,就标准而言,允许运行时但不需要收集a
。如果我在一个想象中的运行时运行我的代码,我所知道的是它符合它,我将不得不忍受这种不确定性。但是,我的代码在最新版本的Microsoft .NET 4运行时上运行。我想知道的是,运行时是否可以预期这样做。运行时是实际的代码,可以准确地知道它会做什么。也许那里有人有这方面的知识,并愿意分享。
答案 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(一个照射患者致死的内侧设备)时。