【发布时间】:2020-03-06 13:55:55
【问题描述】:
我正在编写一些测试以更好地了解 .NET 垃圾收集器的工作原理,以便构建一个没有内存泄漏的框架。但在我的第一个非常简单的测试中,我遇到了意外的行为。
这是我对 GC 的了解:
- 它会定期(在可能的情况下)清理东西
- 它会清除不再引用的实例
这是我为验证我的知识而写的小课:
public class People
{
private People _child;
private WeakReference<People> _parent = new WeakReference<People>(null);
public void AddChild(People child)
{
_child = child;
_child._parent.SetTarget(this);
}
}
基本上,父级可以引用其子级。根据我上面的知识,我希望当父母“死”时,它的孩子也会“死”。
这里的小技巧是使用WeakReference,这样子级就可以访问其父级,但不会创建可能导致内存泄漏的循环引用(这是我试图找出的一点:是两个实例仅相互引用垃圾收集?或者换句话说:在这种情况下我是否必须使用WeakReference?我的猜测是,如果它们直接相互引用,它们将不会被垃圾收集,但我实际上从未检查过它)。
这是我用xUnit写的小测试:
public class GCTests
{
[Fact]
public void TestGC()
{
var parent = new People();
var weakParent = new WeakReference(parent);
var child = new WeakReference(new People());
parent.AddChild(child.Target as People);
// Until now, there is a reference to the parent so the GC is not supposed to collect anything
parent = null;
// But now, no-one is referencing the parent so I expect the GC to collect it and removed it from memory
// Forces the GC to collect unreferenced instances
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Assert.False(weakParent.IsAlive);
Assert.False(child.IsAlive);
}
}
Assert.False(weakParent.IsAlive) 上的测试失败,这意味着有人仍然有对实际父级的引用。
我还尝试使用Thread.Sleep(10000); 给 GC 时间来收集东西,但它仍然无法执行该断言。
所以我的问题是:为什么我的实例没有被垃圾回收?
- 我的哪一个断言是错误的?
- 我在垃圾回收过程中或者
WeakReference的使用过程中误会了什么?
有关信息,我正在使用的 xUnit 测试项目的目标是 .NET Core 3,但我希望它不会改变 GC 过程中的任何内容。
【问题讨论】:
-
你检查过这个thread吗?
-
因为你无法控制它。调用 Collect 并不能确保它会运行。一般来说,它在 Windows 上运行。但是你不能假设它会运行。您只能确保它仅在内存快用完时运行。 docs.microsoft.com/dotnet/api/system.gc.collect。当它运行时,您会在应用程序中看到延迟。因此,Collect 已针对某些因素进行了优化。
-
在我的机器上,带有该代码的控制台应用程序在 .NET Framework 4.7.2 中“工作”,但在 .NET Core 3.0 中失败。这说明了您不能(也不应该)依赖
GC.Collect收集它可以收集的一切 的广泛原则。不过,一般建议在调用GC.Collect时将其移至调用您的逻辑的函数中。它不会让它 100% 工作 - 但它更有可能做你期望它做的事情。WeakReference weakParent, child; YourLogicHere(out weakParent, out child); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); -
stackoverflow.com/questions/15205891/…alive-still-re 和 philosophicalgeek.com/2014/08/14/… 可能也值得一读。
-
你的很多断言都是错误的,但首先是你假设循环引用首先是一个问题。您也可能在附加调试器的情况下运行测试,或者在“调试”版本上运行测试,这两种情况都可能导致 GC 不那么激进。底线是,您无法保证会收集到无法访问的对象。您可以向 GC 提出建议,但它是有意设计的,它对何时收集东西有自己的规则。唯一真正硬性规定的规则是任何可到达的对象不会被收集。
标签: c# garbage-collection weak-references