此问题已经在这里有了答案:
我正在编写一些测试以更好地了解.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
时我会误解什么?有关信息,我正在使用目标.NET Core 3
的xUnit测试项目,但我希望它在GC流程中不做任何更改。
这个问题的核心,您似乎想知道GC是否可以收集唯一引用为循环的对象。我想可以通过创建两个只能互相引用的非常大的对象,然后让它们超出范围并创建第三个大对象来进行测试,以查看是否可以引入一些内存压力来尝试使GC达到目标。免费的东西。如果GC可以释放互相引用的对象,则程序消耗的内存应在某个时候减少。如果GC不能,则仅增加内存使用量:
using System.Threading;
namespace ConsoleApp
{
class Program
{
static void Main()
{
Thread.Sleep(2000);
SetupBigThings();
Thread.Sleep(2000);
string big = new string('a', 1000000000);
while (true)
{
Thread.Sleep(2000);
}
}
static void SetupBigThings()
{
Thread.Sleep(1000);
BigThing x = new BigThing('x');
Thread.Sleep(1000);
BigThing y = new BigThing('y') { OtherBigThing = x };
x.OtherBigThing = y;
Thread.Sleep(1000);
}
}
class BigThing
{
public BigThing OtherBigThing { get; set; }
private string big;
public BigThing(char c)
{
big = new string(c, 750000000);
}
}
}
查看代码,我们应该在3秒处看到内存峰值,然后在4秒处再次出现。在5秒后,大对象超出范围,也许当下一个大对象出现时,它们将在大约7秒处进行GC处理。已创建
几乎就是该图所示:
因此,我认为GC确实可以收集仅相互引用的对象。仅仅说“哪些对象有0个引用?”可能不是那么天真,而是会追逐引用路径,并且仅引用已经考虑用于GC的另一个节点的任何对象也被视为GC'able。我不是GC内部运作方面的专家]