为什么.NET5GC不收集(或至少调用Finalize)明确取消引用的对象?

c#

我想测试垃圾收集器,但这样做很困难。

我写了以下琐碎的测试代码:

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
所做的一切。

垃圾收集器要么不调用我的终结器,要么不完全收集我解除引用的对象。
(注意:我的机器上也是如此,没有在线小提琴)

有趣的是,在尝试Roslyn3.8 Fiddle 时,它似乎有效。

1 出生
2 出生
3 出生
4 出生
构建了一切
deref n1..n3
3 已经死了
2 已经死了
1 已经死
了。

(注意:它甚至可以在没有我所有的“强制一切”到 GC 的情况下工作。)

我现在对 .NET GC 有点不信任危机 🙂
为什么 .NET 编译器缺少我的终结器?它甚至收集我的东西吗?

回答

我同意这些评论,你应该非常小心地尝试从这些例子中理解 GC。你可以,只是要小心,你真的需要了解你在做什么。

这里是 JIT Tier0 ( QuickJIT )的 Debug 模式或 .NET Core 3.1/.NET 5 默认行为- 对象的两个范围都被延长到方法结束(或者换句话说,运行时不关心让它变得如此短尽可能)。在禁用分层编译的情况下运行它,您将看到。

或者向Main方法添加优化属性(这意味着禁用方法的 Tier0):

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Main(string[] args)
{
   ...

您可以在 JIT 生成的代码及其对应的 GCInfo 级别(IL 级别不够)观察这种行为。

未优化的版本(Tier0) -正如你看到的,分配(的结果rax后,JIT_New电话)都存储在堆栈上(rbp-8rbp-10rbp-18rbp-20)。并且这些堆栈位置报告为Untrackedwhich 在 GCInfo 命名法中的意思是“在整个方法生命周期内被视为可访问”

!U /d -gcinfo 00007ffd4a605ec0
Normal JIT generated code
Finalizers.Program.Main(System.String[])
ilAddr is 0000024B68962050 pImport is 0000020B3E9FF5C0
Begin 00007FFD4A605EC0, size 142

...
Code size: 142
Untracked: +rbp+10 +rbp-8 +rbp-10 +rbp-18 +rbp-20
...
.Program.cs @ 12:
mov     rcx,7FFD4A6D4D90h (MT: Finalizers.Foo)
call    coreclr!JIT_New (00007ffd`aa0806f0)
mov     qword ptr [rbp-8],rax
...
.Program.cs @ 13:
mov rcx,7FFD4A6D4D90h (MT: Finalizers.Foo)
call    coreclr!JIT_New (00007ffd`aa0806f0)
mov     qword ptr [rbp-10h],rax
...
.Program.cs @ 14:
mov rcx,7FFD4A6D4D90h (MT: Finalizers.Foo)
call    coreclr!JIT_New (00007ffd`aa0806f0)
mov     qword ptr [rbp-18h],rax
...
.Program.cs @ 15:
mov rcx,7FFD4A6D4D90h (MT: Finalizers.Foo)
call    coreclr!JIT_New (00007ffd`aa0806f0)
mov     qword ptr [rbp-20h],rax
...

优化(第 1 层) - 正如您所看到的,分配结果确实被丢弃了(rax从调用到调用被覆盖)。此外,正如您所看到的,安全点(GC 可能挂起线程的地方)没有报告任何根。他们会,如果 GCInfo 想说在给定的安全点有一个寄存器中的根。

0:007> !U /d -gcinfo 00007ffd4a5e5f20
Normal JIT generated code
Finalizers.Program.Main(System.String[])
ilAddr is 00000151A7672050 pImport is 000001C2D08FF1A0
Begin 00007FFD4A5E5F20, size 1cc

...
.Program.cs @ 12:
0000003a is a safepoint: 
mov     rcx,7FFD4A6B4240h (MT: Finalizers.Foo)
call    coreclr!JIT_New (00007ffd`aa0806f0)
00000049 is a safepoint: 
mov     rcx,rax
mov     edx,1
call    00007ffd`4a5e5b00 (Finalizers.Foo..ctor(Int32), mdToken: 0000000006000003)
.Program.cs @ 13:
00000056 is a safepoint: 
mov rcx,7FFD4A6B4240h (MT: Finalizers.Foo)
call    coreclr!JIT_New (00007ffd`aa0806f0)
00000065 is a safepoint: 
mov     rcx,rax
mov     edx,2
call    00007ffd`4a5e5b00 (Finalizers.Foo..ctor(Int32), mdToken: 0000000006000003)

.Program.cs @ 14:
00000072 is a safepoint: 
mov rcx,7FFD4A6B4240h (MT: Finalizers.Foo)
call    coreclr!JIT_New (00007ffd`aa0806f0)
00000081 is a safepoint: 
mov     rcx,rax
mov     edx,3
call    00007ffd`4a5e5b00 (Finalizers.Foo..ctor(Int32), mdToken: 0000000006000003)

.Program.cs @ 15:
0000008e is a safepoint: 
mov rcx,7FFD4A6B4240h (MT: Finalizers.Foo)
call    coreclr!JIT_New (00007ffd`aa0806f0)
0000009d is a safepoint: 
mov     rcx,rax
mov     edx,4
call    00007ffd`4a5e5b00 (Finalizers.Foo..ctor(Int32), mdToken: 0000000006000003)
...


以上是为什么.NET5GC不收集(或至少调用Finalize)明确取消引用的对象?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>