[C#] ¿Cómo funciona el Garbage Collector? ( III )

Siguiendo con la miniserie dedicada al Garbage Collector, hemos visto los siguientes puntos:

  • Primero sobre cómo se destruyen los objetos en C# y qué mecanismos e implicaciones hay.
  • Segundo, sobre cuándo se dispara el GC y qué efectos tiene sobre la memoria y el rendimiento.
  • Y en este tercero vamos a ver cómo podemos monitorizar el GC.

Notificaciones

El GC puede emitir notificaciones en estos casos:

  • Se ejecuta sobre una generación 2.
  • Pasan objetos de generación 1 a generación 2.

Por motivos de rendimiento no se registra el caso de objetos pequeños  Pero como hemos visto en el artículo anterior, si GC se dispara a menudo repercute en términos de rendimiento en nuestra aplicación. Y si además es porque tenemos objetos de generación 1/2 podemos añadir notificaciones a nuestra aplicación que nos permitirá ver cuándo se prepara y cuándo se ha ejecutado el GC. Veamos un ejemplo:

   1: GC.WaitForFullGCApproach();

Con este método paramos el hilo actual hasta que el GC esté preparado para lanzarse. Podemos hacer que sea un bloqueo indefinido o especificarle un tiempo en milisegundos de espera.

   1: GC.WaitForFullGCComplete();

Y con este otro método detenemos el hilo hasta que el GC completa su tarea en esa colección. Al igual que el método anterior podemos especificarle el tiempo máximo de espera en milisegundos.

Podemos crear un método que se encargue estar pendiente de estas notificaciones a través de un hilo:

   1: public static void WaitForFullGCProc()

   2: {

   3:     while (true)

   4:     {

   5:         while (true)

   6:         {

   7:             GCNotificationStatus s = GC.WaitForFullGCApproach();

   8:             if (s == GCNotificationStatus.Succeeded)

   9:             {

  10:                 Console.WriteLine("GC Notification raised.");

  11:             }

  12:             else if (s == GCNotificationStatus.Canceled)

  13:             {

  14:                 Console.WriteLine("GC Notification cancelled.");

  15:                 break;

  16:             }

  17:             else

  18:             {

  19:                 Console.WriteLine("GC Notification not applicable.");

  20:                 break;

  21:             }

  22:  

  23:             s = GC.WaitForFullGCComplete();

  24:             if (s == GCNotificationStatus.Succeeded)

  25:             {

  26:                 Console.WriteLine("GC Notifiction raised.");

  27:             }

  28:             else if (s == GCNotificationStatus.Canceled)

  29:             {

  30:                 Console.WriteLine("GC Notification cancelled.");

  31:                 break;

  32:             }

  33:             else

  34:             {

  35:                 Console.WriteLine("GC Notification not applicable.");

  36:                 break;

  37:             }

  38:         }

  39:  

  40:  

  41:         Thread.Sleep(500);

  42:  

  43:         if (finalExit)

  44:         {

  45:             break;

  46:         }

  47:     }

  48: }

Este método extremadamente feo y horroroso irá notificando las operaciones del GC y podremos ir viendo cuándo y en qué frecuencia se dispara. Es de notar que esto se llama sólo en caso de generación 1 y 2 y que además sólo en situaciones de bloqueo, por lo que si se llama muchas veces implica que quizá hay algo en nuestra aplicación que está consumiendo demasiados recursos.

Monitorización

Podemos configurar nuestra aplicación para que nos indique algunos datos sobre el consumo de memoria y CPU. A esto se le llama Application Resource Monitoring. Podemos activar la monitorización a través de la siguiente instrucción:

   1: AppDomain.MonitoringIsEnabled = true;

Una vez activada, no se puede desactivar. Aquí tenemos los contadores disponibles:

   1: AppDomain.MonitoringSurvivedProcessMemorySize

Con esto podemos ver cuánta es la memoria gestionada por el proceso que ha sobrevivido a una ejecución del GC.

   1: AppDomain.CurrentDomain.MonitoringSurvivedMemorySize

Y con esta lo mismo que la anterior pero referida al Application Domain. Tanto esta instrucción como la anterior toman en consideración la última aplicación del algoritmo en una fase bloqueante. Traduciendo, significa que se ha invocado a GC.Collect() de modo explícito o directamente lo ha hecho el hilo.

   1: AppDomain.CurrentDomain.MonitoringTotalAllocatedMemorySize

Con esta podemos ver cuántos bytes hemos alojado durante la vida de la aplicación. Hasta aquí todas las operaciones que permiten evaluar la memoria destinada en la aplicación y el proceso.

   1: AppDomain.CurrentDomain.MonitoringTotalProcessorTime

Y esta última el tiempo en el que la CPU está ejecutando el hilo de Application Domain. Veamos un ejemplo con el siguiente código, al que le pondremos estos contadores con el CustomObject de artículos anteriores:

   1: for (int i = 0; i < 10000; i++)

   2: {

   3:     var customObject = new CustomObject();

   4: }

Que produce la siguiente gráfica:

image

Vemos como la memoria total del proceso va en aumento hasta que entra el GC en su primera recolecta en la iteración 871. A partir de ahí, la memoria total crece pero el GC se encarga de ir recolectando. Si suprimimos esta variable para ver con un poco más de nitidez el resto, nos encontramos con esto:

image

Donde se aprecia los pequeños saltos que va optimizando el GC en las recolectas. Y un poco más en detalle:

image

Con lo que tenemos otro método para ver cuánta memoria consumimos y cómo el GC la va organizando. Si vemos que conforme la vida de nuestra aplicación la memoria aumenta y el coste de CPU también puede ser un síntoma de algo que no estamos haciendo bien.

Este es el último artículo de la “serie”. A modo de resumen hemos visto los siguientes puntos del Gargabe Collector:

  • En el primer artículo, hemos visto cómo se finaliza un objeto dentro de .NET Framework.
  • En el segundo, cómo se organizan los objetos internamente y el control algo “manual” del GC.
  • Y en este último sobre cómo podemos recibir notificaciones y medir la memoria empleada para averiguar si tenemos algún problema.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *