【发布时间】:2021-04-15 20:48:23
【问题描述】:
我想测试垃圾收集器,但很难做到。
我编写了以下简单的测试代码:
using System;
class Foo
{
int i;
public Foo(int v)
{
i = v;
Console.WriteLine($"{i} was born");
}
~Foo()
{
Console.WriteLine($"{i} has died");
}
}
public class Program
{
[STAThread]
public static void Main(string[] args)
{
Foo n1 = new Foo(1);
Foo n2 = new Foo(2);
Foo n3 = new Foo(3);
Foo n4 = new Foo(4);
Console.WriteLine("built everything");
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
GC.WaitForPendingFinalizers();
System.Threading.Thread.Sleep(1000);
n1 = null;
n2 = null;
n3 = n4;
Console.WriteLine("deref n1..n3");
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
GC.WaitForPendingFinalizers();
System.Threading.Thread.Sleep(1000);
Console.WriteLine("done.");
}
}
并注意到在 .NET475 和 .NET5 Fiddle 下,
1 出生
2 出生
3 出生
4 出生
建造一切
deref n1..n3
完成。
垃圾收集器要么不调用我的终结器,要么不完全收集我取消引用的对象。
(注意:我的机器上也是这样,没有 Online Fiddle)
有趣的是,在尝试Roslyn3.8 Fiddle 时,它似乎有效。
1 出生
2 出生
3 出生
4 出生
建造一切
deref n1..n3
3人死亡
2人死亡
1人死亡
完成。
(注意:它甚至可以在没有我对 GC 的所有“强制一切”的情况下工作。)
我目前对 .NET GC 存在不信任危机 :)
为什么 .NET 编译器缺少我的终结器?它甚至收集了我的东西吗?
【问题讨论】:
-
“我现在对 .NET GC 存在不信任危机”——为什么?您是否有一个现实世界场景,其中在绝对需要时不收集对象?事实是,从未保证终结器会运行,也没有任何对象将被收集。 GC 一直以“咨询”为基础。在您的示例中,如果您只是将所有对象创建代码移动到一个单独的方法中,GC 将完全按照您的意愿工作。 .NET 中对 GC 进行的数十或数百种优化中的一种可能是导致新行为的原因,但这并不重要。
-
如果您真的关心,请随意查看此处描述的各种更改:devblogs.microsoft.com/dotnet/performance-improvements-in-net-5。当然,如果更改是在早期版本中进行的,则您需要查看该早期版本的记录更改。我没有看到任何需要在这里解决的实际实际问题。很明显,无论出于何种原因,.NET 都将这些对象视为仍然可访问,直到当前方法的堆栈帧消失(即方法已返回)。没有规定必须这样做。
-
这是调试还是发布模式?
-
@PavelAnikhouski:我责怪对象被“收集”的概念造成了很多不必要的混乱。在许多情况下,GC 的行为更像是保龄球置瓶机:它将需要保留的所有内容移动到新位置,然后清除存储空间,而不知道或关心有多少对象占用了它。通过在访问所有强可达对象和彻底清除存储空间之间检查所有具有已注册终结器的对象来适应终结,因此可以使终结对象可访问,但...
标签: c# .net garbage-collection roslyn finalizer