浏览 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/