固定对象时的GC行为

时间:2014-11-14 09:56:08

标签: c# .net garbage-collection .net-internals .net-4.6

在浏览mscorlib的{​​{3}}代码时,我遇到了以下代码:

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);
}

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

1 个答案:

答案 0 :(得分:14)

好的,所以经过多次尝试获得知情人员的正式回复后,我决定自己做一点实验。

我尝试做的是重新制作场景,其中我有几个固定对象和它们之间的一些未固定对象(我使用byte[])来尝试创建未固定对象的效果#&# 39;移动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);
}

我开始使用!eeheap -gc来查看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)

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

0:000> !dumpheap -type System.Byte[]
Address     MT          Size
025424e8    72101860    4108     
025434f4    72101860    4108     
02544500    72101860    4108     
0254550c    72101860    4108     
02546518    72101860    4108  

查看地址我可以看到他们当前在0 {0}从0x02541018开始。我还看到使用!gchandles

固定对象
Handle     Type      Object      Size    Data Type  
002913e4   Pinned    025434f4    4108    System.Byte[]
002913e8   Pinned    025424e8    4108    System.Byte[]

现在,我逐步完成代码,直到我到达运行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)

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

Number of GC Heaps: 1
generation 0 starts at 0x02547524
generation 1 starts at 0x0254100c
generation 2 starts at 0x02541000

第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   

我发现他们所有都停留在同一个地址。 但是,第0代现在从 0x02547524 开始,这意味着它们都被提升为第1代。

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

  

固定一个对象可防止它被垃圾移动   集电极。在代际模型中,它阻止了固定的推广   世代之间的对象。这在此尤为重要   年龄较小的一代,如0代,因为大小   第0代非常小。固定对象导致碎片   在第0代内有可能造成比它更多的伤害   可能会在我们介绍几代之前从检查中看出来   进入图片。 幸运的是,CLR有能力进行推广   使用以下技巧固定对象:如果生成0变为   CLR可以用固定的物体严重破碎   第0代的整个空间被认为是更高代和   从将要成为的新内存区域分配新对象   第0代。这是通过改变短暂的片段来实现的。

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

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