固定对象时的GC行为

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

这让我想知道插头的含义是什么意思? 当试图在内存中固定一个对象时,GC是否不会为该对象指定特定的地址? 这种plug行为究竟在做什么?为什么需要在对象之间“空出来”

好吧,经过多次试图从“内部知识”获得正式答复之后,我决定自己尝试一下。

我试图做的是重新产生的场景,我有一些固定的对象和一些非固定的对象之间(我用一个byte[] )尝试和创build的效果,其中未固定的对象不会移动一个更高在GC堆内生成。

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

代码相当简单:

 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 。 现在,我检查了pinned和none pinned 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开始的事实意味着他们都被提升到了第一代。

然后,我记得在“ Pro .NET Performance ”一书中阅读了一些有关这种行为的内容,它指出了以下内容:

固定一个对象可以防止被垃圾收集器移动。 在世代模型中,它阻止了代之间固定对象的提升。 这在0代的年轻一代尤为重要,因为0代的规模非常小。 固定对象,导致第0代内碎片有可能造成更多的伤害比从检查固定之前,我们介绍了几代人的照片。 幸运的是,CLR能够使用以下技巧来促销固定对象:如果第0代通过固定对象严重碎片化,则CLR可以将第0代的整个空间声明为更高一代,并从新区域分配新对象的内存将成为第一代。这是通过改变临时段来实现的。

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

所以,总结,直到任何人有任何其他的解释,我认为这个评论是不正确的,并没有真正捕捉真正发生在GC内。 如果有人有什么需要说明的,我很乐意补充。