好的,所以在多次试图从“内部人士”获得官方答复之后,我决定自己进行一些实验。
我尝试的是再现这样一种情景,即我有几个固定的对象和一些未固定的对象在它们之间(我使用了一个 byte[]
),以尝试创建这样一种效果:未固定的对象不会移动到GC堆内的较高代中。
代码在我的Intel Core i5笔记本电脑上运行,在运行Visual Studio 2015的32位控制台应用程序中以Debug和Release方式运行。 我使用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。
现在,我检查了被固定(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开始表示它们已经被提升到第1代。
然后,我记得在Pro .NET Performance一书中读到了这种行为,它陈述如下:
固定一个对象会防止它被垃圾回收器移动。在分代模型中,它防止被固定的对象在代之间进行提升。这在更年轻的代(例如第0代)中尤其重要,因为第0代的大小非常小。引起第0代内部碎片化的固定对象可能比我们在将代引入图像之前检查固定对象时显得更有害。 幸运的是,CLR可以使用以下诡计来提升被固定的对象:如果第0代由于带有固定对象的严重碎片而变得严重损坏,CLR可以声明将第0代的整个空间视为较高的代,并从新的内存区域分配新对象,这些新对象将成为第0代。 这是通过更改短暂段来实现的。
这实际上解释了我在WinDBG中看到的行为。
因此,在任何人有其他解释之前,我认为此注释是不正确的,也没有真正捕获GC内部发生的情况。如果有人有任何要详细说明的内容,我很乐意添加。