c# - Comportamiento del GC cuando se fija un objeto
.net garbage-collection (1)
Ok, entonces después de varios intentos de obtener respuestas oficiales de personas con "conocimiento interno", decidí experimentar un poco yo mismo.
Lo que intenté hacer es volver a producir el escenario donde tengo un par de objetos fijados y algunos objetos no fijados entre ellos (utilicé un byte[]
) para tratar de crear el efecto donde los objetos no fijados no se mueven a mayor altura 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 depuración como en versión. 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é a echar un vistazo al espacio de direcciones del montón de 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 por el código en ejecución y miro 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
Al mirar 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 están fijados 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 llego a la línea que ejecuta GC.Collect
. 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, anticipándome a lo que sucede, !eeheap -gc
nuevamente la dirección de generación de GC 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, verifico la dirección de los objetos anclados y ninguno de 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 todos se han quedado en la misma dirección. Pero , el hecho de que la generación 0 ahora comience en 0x02547524 significa que todos han sido promovidos a la generación 1.
Luego, recuerdo haber leído algo sobre ese comportamiento en el libro Pro .NET Performance , que dice lo siguiente:
Fijar un objeto evita que el recolector de basura lo mueva. En el modelo generacional, impide 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 fijados que causan la fragmentación dentro de la generación 0 tienen el potencial de causar más daño de lo que parece al examinarse antes de introducir generaciones en la imagen. Afortunadamente, el CLR tiene la capacidad de promover objetos anclados usando el siguiente truco: si la generación 0 se fragmenta severamente con objetos anclados, el CLR puede declarar el espacio completo de la generación 0 como una generación superior 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 realmente explica el comportamiento que estoy viendo dentro de WinDBG.
Entonces, para concluir y hasta que alguien tenga otra explicación, creo que el comentario no es correcto y realmente no capta lo que realmente sucede dentro del CG. Si alguien tiene algo que explicar, me gustaría añadir.
Mientras PinnableObjectCache
por el 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 enchufe ? Al tratar de fijar un objeto en la memoria, ¿no pincharía el GC la dirección específica especificada para el objeto? ¿Qué está haciendo realmente este comportamiento de plug
y por qué hay una necesidad de "espacio" entre los objetos?