Comportamiento de GC al fijar un objeto


Mientras navega a través del código de PinnableObjectCache desde mscorlib, he encontrado 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 plug? Al intentar fijar un objeto en la memoria, ¿el GC no fijaría la dirección específica especificada para el objeto? ¿Qué está haciendo realmente este comportamiento plug y por qué hay una necesidad de "espaciar" entre los objetos?

Author: Yuval Itzchakov, 2014-11-14

1 answers

Ok, así que después de varios intentos 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 donde tengo un par de objetos anclados y algunos objetos no anclados entre ellos (usé un byte[]) para tratar de crear el efecto donde los objetos no anclados no se mueven la generación superior 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 ejecución de Visual Studio 2015 tanto en Depuración como en Lanzamiento. 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);
}

Empecé con echar un vistazo al espacio de direcciones del montón de GC usando !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 como los objetos se asignan:

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

Mirando 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 a través del código hasta llegar a la línea que corre 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, reviso la dirección de generación 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 anclados y ninguno anclado 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   

Y veo que tienen todos se quedaron 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 , dice lo siguiente:

Fijar un objeto evita que sea movido por la basura Coleccionista. En el modelo generacional, impide la promoción de objetos entre generaciones. Esto es especialmente significativo en el generaciones más jóvenes, como la generación 0, porque el tamaño de la generación 0 es muy pequeña. Objetos anclados que causan fragmentación dentro de la generación 0 tienen el potencial de causar más daño que podría aparecer al examinar pinned antes de que introdujéramos generaciones en la foto. Afortunadamente, el CLR tiene la capacidad de promover anclar objetos usando el siguiente truco: si la generación 0 se convierte en severamente fragmentado con objetos anclados, el CLR puede declarar la todo el espacio de la generación 0 para ser considerado una generación superior y asignar nuevos objetos de una nueva región de memoria que se convertirá en generación 0. Esto se logra cambiando el segmento efímero.

Y esto en realidad explica el comportamiento que estoy viendo dentro de WinDbg.

Así que, para concluir y hasta que alguien tenga 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 elaborar, me encantaría añadir.

 14
Author: Yuval Itzchakov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-16 08:56:46