La recolección de basura no ocurre incluso cuando sea necesario

Hice una aplicación de prueba WPF de 64 bits. Con mi aplicación en ejecución y con el Administrador de tareas abierto, observo el uso de la memoria del sistema. Veo que estoy usando 2GB, y tengo 6GB disponibles.

En mi aplicación, hago clic en el botón Agregar para agregar una nueva matriz de bytes de 1GB a una lista. Veo que el uso de la memoria del sistema aumenta en 1 GB. Hago clic en Agregar un total de 6 veces, completando los 6 GB de memoria que tenía disponible cuando comencé.

Hago clic en el botón Eliminar 6 veces para eliminar cada conjunto de la lista. Los arreglos de bytes eliminados no deben ser referenciados por ningún otro objeto bajo mi control.

Cuando lo elimino, no veo que mi memoria disminuya. Pero eso está bien conmigo, porque entiendo que GC no es determinista y todo eso. Me imagino que el GC se reunirá según sea necesario.

Entonces ahora con la memoria llena, pero esperando que el GC se recopile cuando sea necesario, agrego nuevamente. Mi PC comienza a deslizarse dentro y fuera de un disco golpeando el coma. ¿Por qué el GC no se recopiló? Si ese no era el momento de hacerlo, ¿cuándo es?

Como control de cordura, tengo un botón para forzar GC. Cuando presiono eso, recupero 6GB rápidamente. ¿Eso no prueba que mis 6 arreglos no estaban siendo referenciados y PODRÍAN haberse recogido si el GC lo sabía / quería?

He leído mucho que dice que no debería llamar a GC.Collect (), pero si GC no se acumula en esta situación, ¿qué más puedo hacer?

private ObservableCollection memoryChunks = new ObservableCollection(); public ObservableCollection MemoryChunks { get { return this.memoryChunks; } } private void AddButton_Click(object sender, RoutedEventArgs e) { // Create a 1 gig chunk of memory and add it to the collection. // It should not be garbage collected as long as it's in the collection. try { byte[] chunk = new byte[1024*1024*1024]; // Looks like I need to populate memory otherwise it doesn't show up in task manager for (int i = 0; i  0) { memoryChunks.RemoveAt(0); } } private void GCButton_Click(object sender, RoutedEventArgs e) { GC.Collect(); GC.WaitForPendingFinalizers(); } 

Como control de cordura, tengo un botón para forzar GC. Cuando presiono eso, recupero 6GB rápidamente. ¿Eso no prueba que mis 6 arreglos no estaban siendo referenciados y PODRÍAN haberse recogido si el GC lo sabía / quería?

Probablemente es mejor que pregunte ¿ When does the GC automatically collect "garbage" memory? . La parte superior de mi cabeza:

  • Más comúnmente, cuando la generación 0 está llena o la asignación de un objeto no cabe en el espacio libre disponible. 1
  • De manera bastante común, cuando asignar una porción de memoria causaría una OutOfMemoryException , se OutOfMemoryException un GC completo para probar y recuperar la memoria disponible. Si no hay suficiente memoria contigua disponible después de la recostackción, se lanzará una excepción OOM.

Al iniciar una recolección de basura, el GC determina qué generaciones se deben recolectar (0, 0 + 1 o todas). Cada generación tiene un tamaño determinado por el GC (puede cambiar a medida que se ejecuta la aplicación). Si solo la generación 0 excederá su presupuesto, esa es la única generación cuya basura será recolectada. Si los objetos que sobreviven a la generación 0 harán que la generación 1 supere su presupuesto, la generación 1 también se recostackrá y sus objetos supervivientes se promocionarán a la generación 2 (que es la generación más alta en la implementación de Microsoft). Si también se excede el presupuesto para la generación 2, se recogerá la basura, pero los objetos no se pueden promocionar a una generación superior, ya que uno no existe.

Entonces, aquí radica la información importante, de la manera más común en que se inicia el CG, la Generación 2 solo se recostackrá si las generaciones 0 y 1 están ambas llenas. Además, debe saber que los objetos de más de 85,000 bytes no se almacenan en el montón de GC normal con generación 0, 1 y 2. De hecho, se almacena en lo que se denomina el Montículo de objetos grandes (LOH). La memoria en LOH solo se libera durante una colección FULL (es decir, cuando se recostack la generación 2); nunca cuando solo se están recolectando las generaciones 0 o 1.

¿Por qué el GC no se recopiló? Si ese no era el momento de hacerlo, ¿cuándo es?

Ahora debería ser obvio por qué el GC nunca sucedió automáticamente. Solo está creando objetos en el LOH (tenga en cuenta que los tipos int , de la forma en que los usó, están asignados en la stack y no es necesario recostackrlos). Nunca está llenando la generación 0, por lo que nunca ocurre un GC. 1

También lo está ejecutando en modo de 64 bits, lo que significa que es poco probable que vaya al otro caso que mencioné anteriormente, donde se produce una recostackción cuando no hay suficiente memoria en toda la aplicación para asignar un determinado objeto. Las aplicaciones de 64 bits tienen un límite de espacio de direcciones virtuales de 8 TB, por lo que pasaría un tiempo antes de que llegue a este caso. Es más probable que se quede sin memoria física y espacio de archivos de página antes de que eso suceda.

Como un GC no ha sucedido, Windows comienza a asignar memoria para su aplicación desde el espacio disponible en el archivo de página.

He leído mucho que dice que no debería llamar a GC.Collect (), pero si GC no se acumula en esta situación, ¿qué más puedo hacer?

Llame a GC.Collect() si este tipo de código es lo que necesita para escribir. Mejor aún, no escriba este tipo de código fuera de las pruebas.

En conclusión, no he hecho justicia al tema de la recolección automática de basura en el CLR. Recomiendo leer sobre esto (en realidad es muy interesante) a través de publicaciones en blogs de msdn, o como ya se ha mencionado, el excelente libro de Jeffery Richter, CLR Via C #, Capítulo 21.


1 Supongo que comprende que la implementación .NET del GC es un recolector de basura generacional . En los términos más simples, significa que los objetos recién creados están en una generación de menor número, es decir, generación 0. A medida que se ejecutan recolecciones de basura y se encuentra que un objeto que está en cierta generación tiene una raíz GC (no “basura”), será promovido a la próxima generación. Esta es una mejora en el rendimiento, ya que GC puede llevar mucho tiempo y perjudicar el rendimiento. La idea es que los objetos en las generaciones superiores generalmente tienen una vida más larga y estarán más tiempo en la aplicación, por lo que no es necesario controlar esa generación tanto como las generaciones más bajas. Puedes leer más en este artículo de wikipedia . Notarás que también se llama GC efímero .

2 Si no me cree, después de eliminar uno de los trozos, tenga una función que cree un conjunto completo de cadenas u objetos aleatorios (lo recomiendo contra matrices de primitivas para esta prueba) y verá después de usted alcanzar una cierta cantidad de espacio, se producirá un GC completo, liberando la memoria que había asignado en el LOH.

Esto está sucediendo LOH (Large Object Heap). Estos solo se borran cuando se realiza una recolección de la Generación 2. Como Hans acaba de decir en su comentario, necesitarás más RAM si se trata de código real.

Para risitas puedes llamar a GC.GetGeneration(chunk) para ver que devolverá 2 .

Consulte CLR a través de C #, 3ª edición por Jeffrey Richter (página 588).

Para el código real que necesita asignar una gran cantidad de datos y luego lanzarlo, considere llamar manualmente a GC ( GC.Colgar pregunta de mejores prácticas ) cuando haya terminado con un objeto enorme.

También puede desplazar objetos desde LOH al montón normal al hacer asignaciones en fragmentos más pequeños (menos de 80 K).