【问题标题】:GC behavior when pinning an object固定对象时的 GC 行为
【发布时间】:2015-01-11 16:22:27
【问题描述】:

从mscorlib浏览PinnableObjectCache的代码时,遇到如下代码:

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

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

【问题讨论】:

  • 博文blogs.msdn.com/b/maoni/archive/2005/10/03/… 中有更多信息,向下滚动到“碎片控制”。位我不完全确定它是否回答了这个问题?!
  • @MattWarren 他谈到了 Demotion,其中间隔固定对象之间的对象不会得到提升。但在这个例子中,作者故意在固定对象之间分配一个空间为了确保它们被独立提升。不幸的是,它没有谈论插头行为:\
  • 看起来像是一种在内存中创建填充的方法。给定dummyObject 相当快,newBuffer 之后应该有一些“清除”空间(假设分配是相邻的)。也许最小固定空间是IntPtr.Size 的两倍?
  • 那条评论是由一个只有半点线索的微软新手程序员写的。部分准确,他被要求找到解决方法的问题是真实的。是的,在 GC 压缩算法中确实存在堵塞与间隙,当您分配几个可能被固定的缓冲区时,这是一件令人烦恼的事情。当 GC 运行不会移动时,夹在 可能 之间的那些会被取消固定。其余的评论,不,不是真的。这是 hacky 代码,它出现两次会失去一千个优雅点,它非常无害并且确实试图解决核心问题。
  • @HansPassant 您能否在答案中详细说明 GC 行为?

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


【解决方案1】:

好的,所以在多次尝试从“内幕消息”的人那里获得官方回复后,我决定自己做一些实验。

我试图做的是重现我有几个固定对象和它们之间的一些未固定对象的场景(我使用了byte[])来尝试创建未固定对象不移动的效果GC 堆内的更高一代。

代码在我的英特尔酷睿 i5 笔记本电脑上运行,在 32 位控制台应用程序中运行 Visual Studio 2015 调试和发布。我使用 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 代,因为它从 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 内部真正发生的事情。如果有人有什么要详细说明的,我很乐意补充。

【讨论】:

  • 很抱歉,这是一个愚蠢的问题,但obj1 是什么?应该是byteArr1?
  • @dumbledad 你完全正确。我对代码进行了几次迭代,但忘记重命名答案中的变量。
  • 你应该在调用 GC 之后添加一些东西来保持对象的存活,因为我认为它只是让它们保持存活的调试器。
  • @Ian 我不确定我是否遵循。问题的重点是看看在 GC 收集期间,当一些对象被固定而其他对象没有被固定时会发生什么,无论未固定的对象是否处于活动状态。
  • 如果在调用 GCHandle.Alloc(byteArr5, GCHandleType.Pinned); 之前 byteArr3 和 byteArr4 被垃圾回收了怎么办? (我只是不做不是 100% 可重复的实验......)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-20
  • 2011-07-14
  • 2012-11-10
相关资源
最近更新 更多