如何解决为什么 .NET 5 GC 不收集或至少调用 Finalize明确取消引用的对象?
我想测试垃圾收集器,但这样做很困难。
我写了以下琐碎的测试代码:
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,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 默认行为 - 对象的两个范围都被延长到方法结束(或者换句话说,运行时不在乎使它尽可能短)。用 Tiered compilation disabled 运行它,你会看到。
或者向 Main
方法添加优化属性(转换为禁用方法的 Tier0):
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Main(string[] args)
{
...
您可以在 JIT 生成的代码及其对应的 GCInfo 级别(IL 级别不够)观察此行为。
未优化版本(Tier0) - 如您所见,分配结果(rax
调用后的JIT_New
)存储在堆栈 (rbp-8
、rbp-10
、rbp-18
和 rbp-20
)。并且这些堆栈位置报告为 Untracked
,在 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
被调用覆盖)。此外,正如您所看到的,safepoint(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,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,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,4
call 00007ffd`4a5e5b00 (Finalizers.Foo..ctor(Int32),mdToken: 0000000006000003)
...
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。