c# - invocar - Misterio del recolector de basura.NET
garbage collector yugioh (4)
Dependiendo de la CLR que esté utilizando, puede haber algunos problemas relacionados con la gran acumulación de objetos grandes.
Eche un vistazo a este artículo, que explica los problemas con las grandes asignaciones de bloques (y la lista con 200000 elementos es un gran bloque, el otro puede o no puede ser, algunas matrices parecen estar en LOH cuando llegan a 8k, otros después de 85k).
http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/
En mi trabajo tuvimos un problema con OutOfMemoryExceptions. He escrito un simple fragmento de código para imitar algún comportamiento, y he terminado con el siguiente misterio. Mira este código simple que explota cuando se queda sin memoria.
class Program
{
private static void Main()
{
List<byte[]> list = new List<byte[]>(200000);
int iter = 0;
try
{
for (;;iter++)
{
list.Add(new byte[10000]);
}
}
catch (OutOfMemoryException)
{
Console.WriteLine("Iterations: " + iter);
}
}
}
En mi máquina terminó con
Iterations: 148008
Luego agregué una llamada GC.Collect
al bucle después de cada mil iteraciones:
//...
for (;;iter++)
{
list.Add(new byte[10000]);
if (iter % 1000 == 0)
GC.Collect();
}
//...
Y sorpresa:
Iterations: 172048
Cuando llamé a GC.Collect
después de cada 10 iteraciones, incluso obtuve 193716 ciclos. Hay dos cosas extrañas:
¿Cómo puede una llamada manual a
GC.Collect
tener un impacto tan severo (hasta un 30% más asignado)?¿Qué demonios puede recopilar GC cuando no hay referencias "perdidas" (incluso he preestablecido la capacidad de la Lista)?
El CLR ocasionalmente coloca matrices en el LOH. Si alguna vez mira un volcado de memoria a través de WinDbg, verá que hay matrices que tienen menos de 85,000 bytes. Es un comportamiento indocumentado, pero así es como funciona.
Está obteniendo los OutOfMemoryErrors porque está fragmentando el montón de LOH y el montón de LOH nunca se compacta.
Con respecto a su pregunta de:
2) ¿Qué demonios puede recopilar GC cuando no hay referencias "perdidas" (incluso he preconfigurado la capacidad de la Lista)?
Hay referencias sobreescritas al new byte[10000]
que pasa para agregarlas a la lista. Se compila una variable local y se asigna al new byte[10000]
. Para cada iteración en el bucle, crea un nuevo byte [] con un tamaño predefinido de 10000 y se asigna a la variable local. Cualquier valor anterior para la variable se sobrescribe y esa memoria es elegible para la recopilación la próxima vez que el GC se ejecute para la generación en que vive la variable (en este caso, posiblemente el LOH).
Tuve un problema similar en .NET con la diferencia que mi byte [] tenía tamaños aleatorios.
Probé de dos maneras:
escriba un administrador de pila propio (asigne memoria con un búfer grande y solo ajuste los punteros)
utilizar un archivo asignado en memoria (en mi opinión, la mejor solución)
Si es posible, puede probar .NET 4.5 http://blogs.msdn.com/b/dotnet/archive/2012/07/20/the-net-framework-4-5-includes-new-garbage-collector-enhancements-for-client-and-server-apps.aspx
Una parte del proceso de recolección de basura es la fase de compactación. Durante esta fase, los bloques de memoria asignada se mueven para reducir la fragmentación. Cuando se asigna la memoria, no siempre se asigna justo después de que se haya dejado la última parte de la memoria asignada. Así que puedes apretar un poco más porque el recolector de basura está haciendo más espacio haciendo un mejor uso del espacio disponible.
Estoy intentando realizar algunas pruebas, pero mi máquina no puede manejarlas. Intente esto, le indicará al GC
que fije los objetos en la memoria para que no se muevan
byte[] b = new byte[10000];
GCHandle.Alloc(b, GCHandleType.Pinned);
list.Add(b);
En cuanto a su comentario, cuando el GC
mueve las cosas, no borra nada, solo hace un mejor uso de todo el espacio de la memoria. Vamos a intentarlo una y otra vez. Cuando asigna su matriz de bytes la primera vez, digamos que se inserta en la memoria desde el punto 0 hasta 10000. La próxima vez que asigne la matriz de bytes, no está garantizado que comience en 10001, puede comenzar en 10500. tiene 499 bytes que no están siendo utilizados y su aplicación no los utilizará. Entonces, cuando el GC
haga compactación, moverá la matriz 10500 a 10001 para poder usar esos 499 bytes adicionales. Y de nuevo, esto está muy simplificado.