固定对象时的GC行为

Yuv*_*kov 36 .net c# garbage-collection .net-internals .net-4.6

在浏览PinnableObjectCachefrom 的代码时mscorlib,我遇到了以下代码:

for (int i = 0; i < m_restockSize; i++)
{
    // Make a new buffer.
    object newBuffer = m_factory();

    // Create space between the objects.  We do this because otherwise it forms 
    // a single plug (group of objects) and the GC pins the entire plug making 
    // them NOT move to Gen1 and Gen2. By putting space between them
    // we ensure that object get a chance to move independently (even if some are pinned).  
    var dummyObject = new object();
    m_NotGen2.Add(newBuffer);
}
Run Code Online (Sandbox Code Playgroud)

它让我想知道插件的参考意味着什么?在尝试将对象固定在内存中时,GC是否会固定为对象指定的特定地址?这种plug行为实际上是做什么的,为什么需要在对象之间"空出"

Yuv*_*kov 14

好的,所以经过多次尝试获得"内部知识"的人的官方回复,我决定自己做一点实验.

我试图做的是重新制作场景,其中我有几个固定对象和它们之间的一些未固定对象(我用过a byte[])尝试创建效果,其中未固定的对象不会移动更高的一代内部GC堆.

代码运行在我的英特尔酷睿i5笔记本电脑上,在调试和发布中运行Visual Studio 2015的32位控制台应用程序中运行.我使用WinDBG实时调试了代码.

代码很简单:

private static void Main(string[] args)
{
    byte[] byteArr1 = new byte[4096];
    GCHandle obj1Handle = GCHandle.Alloc(byteArr1 , GCHandleType.Pinned);
    object byteArr2 = new byte[4096];
    GCHandle obj2Handle = GCHandle.Alloc(byteArr2, GCHandleType.Pinned);
    object byteArr3 = new byte[4096];
    object byteArr4 = new byte[4096];
    object byteArr5 = new byte[4096];
    GCHandle obj4Handle = GCHandle.Alloc(byteArr5, GCHandleType.Pinned);
    GC.Collect(2, GCCollectionMode.Forced);
}
Run Code Online (Sandbox Code Playgroud)

我开始使用以下方法查看GC堆地址空间!eeheap -gc:

generation 0 starts at 0x02541018 
generation 1 starts at 0x0254100c
generation 2 starts at 0x02541000 

ephemeral segment allocation context: none

segment      begin      allocated   size 
02540000     02541000   02545ff4    0x4ff4(20468)
Run Code Online (Sandbox Code Playgroud)

现在,我逐步运行代码并观察对象是否已分配:

0:000> !dumpheap -type System.Byte[]
Address     MT          Size
025424e8    72101860    4108     
025434f4    72101860    4108     
02544500    72101860    4108     
0254550c    72101860    4108     
02546518    72101860    4108  
Run Code Online (Sandbox Code Playgroud)

看看地址,我可以看到他们目前都在第0代开始0x02541018.我还看到对象被固定使用!gchandles:

Handle     Type      Object      Size    Data Type  
002913e4   Pinned    025434f4    4108    System.Byte[]
002913e8   Pinned    025424e8    4108    System.Byte[]
Run Code Online (Sandbox Code Playgroud)

现在,我逐步完成代码,直到我到达运行的行GC.Collect:

0:000> p
eax=002913e1 ebx=0020ee54 ecx=00000002 edx=00000001 esi=025424d8 edi=0020eda0
eip=0062055e esp=0020ed6c ebp=0020edb8 iopl=0  nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b  efl=00000206
0062055e e80d851272      call    mscorlib_ni+0xa28a70 (GC.Collect) (72748a70)
Run Code Online (Sandbox Code Playgroud)

现在,预测会发生什么,我再次检查GC生成地址!eeheap -gc,我看到以下内容:

Number of GC Heaps: 1
generation 0 starts at 0x02547524
generation 1 starts at 0x0254100c
generation 2 starts at 0x02541000
Run Code Online (Sandbox Code Playgroud)

第0代的起始地址已从0x02541018移至0x02547524.现在,我检查固定和无固定byte[]对象的地址:

0:000> !dumpheap -type System.Byte[]
Address  MT           Size
025424e8 72101860     4108     
025434f4 72101860     4108     
02544500 72101860     4108     
0254550c 72101860     4108     
02546518 72101860     4108   
Run Code Online (Sandbox Code Playgroud)

我看到他们住在同一个地址.但是,第0代现在从0x02547524开始的事实意味着他们已经被提升到第1代.

然后,我记得在Pro .NET Performance一书中阅读有关该行为的内容,它说明了以下内容:

固定对象可防止垃圾收集器移动它.在世代模型中,它阻止了代际之间的固定对象的推广.这对于年轻一代尤其重要,例如第0代,因为第0代的大小非常小.在第0代引起碎片化的固定对象有可能造成比在我们将图像引入图片之前检查固定所造成的更多伤害.幸运的是,CLR能够使用以下技巧来提升固定对象:如果第0代被固定对象严重碎片化,CLR可以将第0代的整个空间声明为更高代,并从新区域分配新对象将成为第0代的内存.这是通过更改短暂段来实现的.

这实际上解释了我在WinDBG中看到的行为.

所以,总而言之,直到任何人有任何其他解释,我认为评论不正确,并没有真正捕捉到GC内部真正发生的事情.如果有人有任何要详细说明,我很乐意补充.