c# - 固定对象时的 GC 行为

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

浏览 PinnableObjectCache 的代码时从 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);
}

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

最佳答案

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

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

代码在我的 Intel Core 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 一书中读过一些关于这种行为的内容,它指出如下:

Pinning an object prevents it from being moved by the garbage collector. In the generational model, it prevents promotion of pinned objects between generations. This is especially significant in the younger generations, such as generation 0, because the size of generation 0 is very small. Pinned objects that cause fragmentation within generation 0 have the potential of causing more harm than it might appear from examining pinned before we introduced generations into the the picture. Fortunately, the CLR has the ability to promote pinned objects using the following trick: if generation 0 becomes severely fragmented with pinned objects, the CLR can declare the entire space of generation 0 to be considered a higher generation and allocate new objects from a new region of memory that will become generation 0. This is achieved by changing the ephemeral segment.

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

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

关于c# - 固定对象时的 GC 行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26927243/

相关文章:

c# - 自定义数据表输出

c# - 等待进程完成然后显示消息 (C#)

C#:如果需要内存,GC 会收集,还是会给出内存不足异常?

java - 100% 完整的伊甸园空间,0% 使用的幸存者空间 - 垃圾收集未完成

C# 和 .Net 垃圾收集器性能

c# - C# 是否强制 `unmanaged` 类型为 "blittable"?

c# - 使用友好的 URL 进行路由

c# - 无法将 JSON 序列化为字典

.net - 独特的 CodeCoverage 突出显示

c# - .NET 在运行时再次加载程序集