在try WeakHandles时,我在.NET 6上发现了这个特性.

static void Main(string[] args) {
    var foo = new int[3];
    var fooWeakHandle = GCHandle.Alloc(foo, GCHandleType.Weak);
    GC.Collect();
    Console.WriteLine(fooWeakHandle.Target);
}

.NET 6的结果(在发布模式下编译,以避免急于根收集)令人惊讶

System.Int32[]

在.NET框架4.7.2下,它在版本中为空/无,而在收件箱中为System.Int32[]. 我对框架和.NET(Core)使用LinqPad(optimate +-)也得到了相同的结果.

为什么只被WeakHandle垃圾引用的数组没有被收集?

编辑 :

我try 按照建议将代码移动到不同的方法中-结果仍然相同(甚至抛出了一些不必要的MethodImpi选项) .NET框架4.7.2和.NET 6的IL相同(mscorlib与System. SYS之间仅存在差异).甚至添加了一个空类来try 使用new B()而不是新的int[].相同的结果.

static void Main(string[] args) {
    DifferentFunction();
}

[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
static void DifferentFunction() {
    var foo = new int[3];
    var fooWeakHandle = GCHandle.Alloc(foo, GCHandleType.Weak);
    GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true);
    Console.WriteLine(fooWeakHandle.Target);
}

class B
{

}

.method private hidebysig static 
    void DifferentFunction () cil managed noinlining nooptimization 
{
    // Method begins at RVA 0x2058
    // Header size: 12
    // Code size: 35 (0x23)
    .maxstack 4
    .locals init (
        [0] valuetype [System.Runtime]System.Runtime.InteropServices.GCHandle fooWeakHandle
    )

    // GCHandle gCHandle = GCHandle.Alloc(new int[3], GCHandleType.Weak);
    IL_0000: ldc.i4.3
    IL_0001: newarr [System.Runtime]System.Int32
    IL_0006: ldc.i4.0
    IL_0007: call valuetype [System.Runtime]System.Runtime.InteropServices.GCHandle [System.Runtime]System.Runtime.InteropServices.GCHandle::Alloc(object, valuetype [System.Runtime]System.Runtime.InteropServices.GCHandleType)
    IL_000c: stloc.0
    // GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true);
    IL_000d: ldc.i4.2
    IL_000e: ldc.i4.1
    IL_000f: ldc.i4.1
    IL_0010: ldc.i4.1
    IL_0011: call void [System.Runtime]System.GC::Collect(int32, valuetype [System.Runtime]System.GCCollectionMode, bool, bool)
    // Console.WriteLine(gCHandle.Target);
    IL_0016: ldloca.s 0
    IL_0018: call instance object [System.Runtime]System.Runtime.InteropServices.GCHandle::get_Target()
    IL_001d: call void [System.Console]System.Console::WriteLine(object)
    // }
    IL_0022: ret
} // end of method Program::DifferentFunction

推荐答案

编辑:我找到了another answer,可以更准确地捕捉到.NET框架和.NET Core之间的区别- Tiered Compilation

在.NET 5(和.NET Core 3+)中默认启用此功能,但并非如此 可在.NET 4.8中使用.

在您的情况下,结果是您的方法已被提及 "快速"编译,并且优化不够,无法让您的代码运行 正如您所期望的那样(即myBody变量的生命周期延长,直到 方法结束).即使您在发行版中编译,情况也是如此 已启用优化且未连接任何调试器的模式.

当我用编译我的方法时

[MethodImpl(MethodImplOptions.AggressiveOptimization)]

预期的事情发生.然而,因为答案包含一些警告

分层编制将(for some methods under some conditions) 首先编译方法的原始、低优化版本,然后再编译 如有必要,将准备更好的优化版本.

我将留下我的旧答案,该答案描述了完全和部分可中断的方法,作为差异的可能原因和我的主要收获是

虽然在收件箱模式下编译总是会延长 局部变量到方法结束,释放模式CAN标记 如果JT代码,变量(根)将被视为死(不再可访问) 启发式认为这是有益的.但这并不总是 案件已得到证实.

OLD Answer:

虽然JonasH的回答指向了正确的方向,但我想提供我在调查期间设法挖掘到的可能感兴趣的细节.

似乎所有这一切的关键是方法可以部分或完全可中断(from this answer):

然而,有关堆栈根活动性的信息仅为 所谓的安全点.有两种方法: 部分可中断-唯一的安全点是在调用其他方法期间.这使得方法不那么"可暂停",因为 运行时需要等待这样的安全点才能暂停方法,但是 GC信息占用更少的内存. 完全可中断-方法的每个指令都被视为安全点,这显然使方法非常"可暂停",但 需要大量存储(数量与代码本身相似)

正如《尤利西斯之书》所说:"JT Select 是否完全排放-- 或基于启发式方法的部分可中断代码以找到最佳 代码质量、GC信息大小和GC暂停之间的权衡 延迟."

我已经修改了我的初始示例来测试这一点(将代码移至另一个方法,无内联是测试的产物,应该不重要).还用伪类实例化替换了数组,以防数组在某种程度上更加特殊.

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace ConsoleApp2
{
    internal class Program
    {
        static GCHandle fooWeakHandle;


        static void Main()
        {
            DifferentFunction();
            //GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true);
            Console.WriteLine(fooWeakHandle.Target);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        static void DifferentFunction()
        {
            //var foo = new int[3];
            var foo = new B();

            // just having a loop switches to expected behavior
            //for (int i = 0; i < 1; i++) { }

            fooWeakHandle = GCHandle.Alloc(foo, GCHandleType.Weak);

            GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true);
        }

        class B
        {

        }
    }
}

现在,如果我们取消注释//for (int i = 0; i < 1; i++) { },我们将获得一个完全可中断的方法,正如WinGbg +sos调试命令!u -gcinfo 00007FFECDF682C7的输出所证明的那样

**00000018 interruptible**
00007ffe`1f538298 488d4d88        lea     rcx,[rbp-78h]
00007ffe`1f53829c 498bd2          mov     rdx,r10
00007ffe`1f53829f e82cf8b05f      call    coreclr!JIT_InitPInvokeFrame (00007ffe`7f047ad0)
00007ffe`1f5382a4 488bf0          mov     rsi,rax
00007ffe`1f5382a7 488bcc          mov     rcx,rsp
00007ffe`1f5382aa 48894da8        mov     qword ptr [rbp-58h],rcx
00007ffe`1f5382ae 488bcd          mov     rcx,rbp
00007ffe`1f5382b1 48894db8        mov     qword ptr [rbp-48h],rcx
00007ffe`1f5382b5 cc              int     3
00007ffe`1f5382b6 b9b040651f      mov     ecx,1F6540B0h
00007ffe`1f5382bb fe              ???
00007ffe`1f5382bc 7f00            jg      00007ffe`1f5382be
00007ffe`1f5382be 00e8            add     al,ch
00007ffe`1f5382c0 3c33            cmp     al,33h
00007ffe`1f5382c2 b95f488bc8      mov     ecx,0C88B485Fh

D:\ConsoleApp2\ConsoleApp2\Program.cs @ 27:
**00000044 +rax**
**00000047 +rcx**
00007ffe`1f5382c7 33d2            xor     edx,edx
00007ffe`1f5382c9 e822dab55f      call    coreclr!MarshalNative::GCHandleInternalAlloc (00007ffe`7f095cf0)
**0000004e -rcx -rax**
00007ffe`1f5382ce 488bf8          mov     rdi,rax
00007ffe`1f5382d1 48b928af5c1ffe7f0000 mov rcx,7FFE1F5CAF28h
00007ffe`1f5382db ba01000000      mov     edx,1
00007ffe`1f5382e0 e8fb34b95f      call    coreclr!JIT_GetSharedNonGCStaticBase_SingleAppDomain (00007ffe`7f0cb7e0)
00007ffe`1f5382e5 48b9902e22ea2c020000 mov rcx,22CEA222E90h
00007ffe`1f5382ef 488b09          mov     rcx,qword ptr [rcx]
**00000072 +rcx**
00007ffe`1f5382f2 48897908        mov     qword ptr [rcx+8],rdi

D:\ConsoleApp2\ConsoleApp2\Program.cs @ 29:
00007ffe`1f5382f6 b902000000      mov     ecx,2
**0000007b -rcx**
00007ffe`1f5382fb ba0a000000      mov     edx,0Ah
**00007ffe`1f538300 48b848f35e1ffe7f0000 mov rax,7FFE1F5EF348h (MD: System.GC._Collect(Int32, Int32))**

我用 ** 标记了跟踪可能发生的有趣行.

如果我们 comments 哑循环,我们似乎会得到一个部分可中断的方法:

00007ffe`cdf68298 488d4d88        lea     rcx,[rbp-78h]
00007ffe`cdf6829c 498bd2          mov     rdx,r10
00007ffe`cdf6829f e82cf8af5f      call    coreclr!JIT_InitPInvokeFrame (00007fff`2da67ad0)
00007ffe`cdf682a4 488bf0          mov     rsi,rax
00007ffe`cdf682a7 488bcc          mov     rcx,rsp
00007ffe`cdf682aa 48894da8        mov     qword ptr [rbp-58h],rcx
00007ffe`cdf682ae 488bcd          mov     rcx,rbp
00007ffe`cdf682b1 48894db8        mov     qword ptr [rbp-48h],rcx
00007ffe`cdf682b5 cc              int     3
00007ffe`cdf682b6 b9b04008ce      mov     ecx,0CE0840B0h
00007ffe`cdf682bb fe              ???
00007ffe`cdf682bc 7f00            jg      00007ffe`cdf682be
00007ffe`cdf682be 00e8            add     al,ch
00007ffe`cdf682c0 3c33            cmp     al,33h
00007ffe`cdf682c2 b85f488bc8      mov     eax,0C88B485Fh

D:\ConsoleApp2\ConsoleApp2\Program.cs @ 27:
**00000044 is a safepoint:** 
00007ffe`cdf682c7 33d2            xor     edx,edx
00007ffe`cdf682c9 e822dab45f      call    coreclr!MarshalNative::GCHandleInternalAlloc (00007fff`2dab5cf0)
**0000004e is a safepoint:** 
00007ffe`cdf682ce 488bf8          mov     rdi,rax
00007ffe`cdf682d1 48b928afffcdfe7f0000 mov rcx,7FFECDFFAF28h
00007ffe`cdf682db ba01000000      mov     edx,1
00007ffe`cdf682e0 e8fb34b85f      call    coreclr!JIT_GetSharedNonGCStaticBase_SingleAppDomain (00007fff`2daeb7e0)
**00000065 is a safepoint:** 
00007ffe`cdf682e5 48b9902e8a4c6a020000 mov rcx,26A4C8A2E90h
00007ffe`cdf682ef 488b09          mov     rcx,qword ptr [rcx]
00007ffe`cdf682f2 48897908        mov     qword ptr [rcx+8],rdi

D:\ConsoleApp2\ConsoleApp2\Program.cs @ 29:
00007ffe`cdf682f6 b902000000      mov     ecx,2
00007ffe`cdf682fb ba0a000000      mov     edx,0Ah
00007ffe`cdf68300 48b848f301cefe7f0000 mov rax,7FFECE01F348h (MD: System.GC._Collect(Int32, Int32))

我只使用过Winddbg +Sos几次,但根据this excellent video,我希望安全点还包括+rax、-rax信息,用于跟踪什么是实时的和什么不是.但在我的输出中,情况并非如此.

因此,就我而言,这通常不应该包括任何活根.也许在安全点之上还有另一种机制可以保持物体的活力?也许有人可以发表 comments .

对我来说,主要的收获是,虽然在收件箱模式下进行编译总是会将局部变量的生命周期延长到方法的结尾,但如果JT代码试探法认为这是有益的,则发布模式可以将变量(根)标记为死(不再可访问).但事实并非总是如此.

GC主要维护者Maoni提出的For a very good reference个问题(从 comments 到我的问题):

"This is because when JIT generates code for you, it's free to lengthen the lifetime till end of the method."

Csharp相关问答推荐

将json作为字符串发送到API会导致错误,但发送dotnet对象不会

使用源生成器,如何使用所有转换后的 node 的聚合生成源?

.NET通过版本自动增量设置包版本

ASP.NET Core:如何在IPageFilter中注入ApplicationDbContext

dotnet集合中内部数组的局部变量副本的用途是什么?'

从Blob存储中提取tar.gz文件并将提取结果上载到另一个Blob存储

有没有办法使.NET 6应用程序在特定的.NET 6运行时版本上运行

用C#调用由缓冲区指针参数组成的C API

ASP.NET Core AutoMapper:如何解决错误 CS0121调用在以下方法或属性之间不明确

Azure函数中实体框架核心的依赖注入

ASP.NET核心MVC SqlException:违反主键约束';PK_USER';.无法在对象';数据库中插入重复的密钥.用户';

如何使用MoQ模拟Resources GroupCollection?

HelperText属性不支持复杂内容(混合C#和标记)

MSTest--将消息直接写入StdOut和使用TestContext有什么不同?

HttpClient SendAsync与多线程环境中的ArchiveZip

如何将默认区域性更改为fr-FR而不是en-US?

Visual Studio 17.8.0制表符自动完成问题--三缩进

Maui:更改代码中的绑定字符串不会更新UI,除非重新生成字符串(MVVM)

如何阻止可传递依赖项出现在项目中

如何为控制器PUT操作绑定对象数组