Comportamiento de GC al fijar un objeto

Mientras PinnableObjectCache el código de PinnableObjectCache de mscorlib , encontré el siguiente código:

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

¿Me hizo preguntarme qué significa la referencia a un enchufe ? Al tratar de fijar un objeto en la memoria, ¿no fijaría el GC la dirección específica especificada para el objeto? ¿Qué está haciendo realmente este comportamiento de plug y por qué es necesario “espaciar” entre los objetos?

Ok, después de varios bashs de obtener respuestas oficiales de personas con “conocimiento interno”, decidí experimentar un poco yo mismo.

Lo que traté de hacer es volver a producir el escenario en el que tengo un par de objetos anclados y algunos objetos no clavados entre ellos (usé un byte[] ) para intentar crear el efecto donde los objetos no clavados no mueven el a más alto Generación dentro del montón GC.

El código se ejecutó en mi computadora portátil Intel Core i5, dentro de una aplicación de consola de 32 bits que ejecuta Visual Studio 2015, tanto en Debug como en Release. Depuré el código en vivo usando WinDBG.

El código es bastante simple:

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

Comencé con echar un vistazo al espacio de direcciones del montón GC utilizando !eeheap -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) 

Ahora paso a través del código en ejecución y observo cómo se asignan los objetos:

 0:000> !dumpheap -type System.Byte[] Address MT Size 025424e8 72101860 4108 025434f4 72101860 4108 02544500 72101860 4108 0254550c 72101860 4108 02546518 72101860 4108 

En cuanto a las direcciones, puedo ver que todas están actualmente en la generación 0, ya que comienza en 0x02541018 . También veo que los objetos se fijan usando !gchandles :

 Handle Type Object Size Data Type 002913e4 Pinned 025434f4 4108 System.Byte[] 002913e8 Pinned 025424e8 4108 System.Byte[] 

Ahora paso por el código hasta que llegue a la línea que ejecuta 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) 

Y ahora, anticipando lo que sucede, !eeheap -gc dirección de generación de GC nuevamente usando !eeheap -gc y veo lo siguiente:

 Number of GC Heaps: 1 generation 0 starts at 0x02547524 generation 1 starts at 0x0254100c generation 2 starts at 0x02541000 

La dirección de inicio para la generación 0 se ha movido de 0x02541018 a 0x02547524 . Ahora, compruebo la dirección de los objetos de byte[] fijados y ninguno anclado:

 0:000> !dumpheap -type System.Byte[] Address MT Size 025424e8 72101860 4108 025434f4 72101860 4108 02544500 72101860 4108 0254550c 72101860 4108 02546518 72101860 4108 

Y veo que todos se han quedado en la misma dirección. Pero , el hecho de que la generación 0 ahora comienza en 0x02547524 significa que todos han sido promovidos a la generación 1.

Entonces, recuerdo haber leído algo sobre ese comportamiento en el libro Pro .NET Performance , que dice lo siguiente:

La fijación de un objeto evita que el recolector de basura lo mueva. En el modelo generacional, evita la promoción de objetos fijados entre generaciones. Esto es especialmente significativo en las generaciones más jóvenes, como la generación 0, porque el tamaño de la generación 0 es muy pequeño. Los objetos anclados que causan fragmentación dentro de la generación 0 tienen el potencial de causar más daño del que podría parecer al examinar los anclados antes de que introdujéramos generaciones en la imagen. Afortunadamente, el CLR tiene la capacidad de promover objetos anclados mediante el siguiente truco: si la generación 0 se fragmenta severamente con los objetos anclados, el CLR puede declarar que todo el espacio de la generación 0 se considera una generación más alta y asignar nuevos objetos de una nueva región de memoria que se convertirá en la generación 0. Esto se logra cambiando el segmento efímero.

Y esto explica realmente el comportamiento que estoy viendo dentro de WinDBG.

Entonces, para concluir y hasta que alguien tenga alguna otra explicación, creo que el comentario no es correcto y realmente no captura lo que realmente está sucediendo dentro del GC. Si alguien tiene algo que explicar, me complacería agregar.