编辑:我找到了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."