recoleccion programacion memoria liberar leaks basura avoid c# memory-leaks

c# - memoria - recoleccion de basura en programacion



¿Cómo evitar fugas de memoria? (18)

¿Cuáles son algunos consejos que puedo usar para evitar pérdidas de memoria en mis aplicaciones? ¿Hay alguna trampa o trampas que pueda cuidar?


  1. Envuelva todo lo que sea desechable en una construcción de uso.
  2. Evitar objetos COM
  3. Verifique que todos los registros de eventos estén siendo eliminados correctamente.
  4. Verifique que todos los enlaces de datos se eliminen correctamente.
  5. Mantenlo simple

Si la lógica de su aplicación se vuelve innecesariamente compleja, puede comenzar a terminar con pérdidas de memoria. Si mantiene sus clases pequeñas y sigue las prácticas generales de codificación, probablemente no se encontrará con ninguna pérdida de memoria con el código administrado. Es posible, pero no tan probable como solía ser.

Si sospecha que hay una pérdida de memoria, use un generador de perfiles para ver si los objetos se mantienen por más tiempo de lo necesario.

La última vez que me encontré con una pérdida de memoria grave fue .NET 1.1, resultó que había un error en el marco.


Como han dicho otros, llame a Dispose () (o use una instrucción de uso), pero además considere si las clases usan recursos y, si es así, implemente IDisposeable. Esto es más a menudo el problema en mi código es que tengo una clase con un miembro que no se limpia hasta un GC.


Como menciona ocdecio, asegúrese de llamar a Dispose en Idisposable objects, y recuerde eliminar los controladores de eventos cuando haya terminado con un objeto. Al crear clases que funcionen con recursos no administrados, asegúrese de implementar Idisposable, para que el usuario sepa que hay recursos críticos que deberán eliminarse.

Además, a pesar de que las recolecciones de basura hacen bastante trabajo para usted, debe deshacerse de las referencias a los objetos que haya terminado. De lo contrario, seguirán teniendo una raíz, y no tendrán GC.


El caso más común de memoria no destruida por el GC es cuando tiene controladores de eventos que no se desengancharon correctamente. Puede desenganchar en Dispose () si lo desea.

Describo el problema más detalladamente, y tengo una forma de escribir pruebas para determinar si se filtra el objeto aquí .



La mayoría de las pérdidas de memoria que he encontrado en .NET se han relacionado con el uso de objetos COM y no liberarlos correctamente. Tan pronto como veo una referencia a un objeto COM, pienso "fuga de memoria".


Las pérdidas de memoria son errores, por lo que, en general, la pregunta podría responderse de la misma manera que "cómo codificar sin errores". A largo plazo, no es posible no tener errores, pero puede limitar la posibilidad de tenerlos en el código publicado.

Comience por preocuparse por la calidad del código desarrollado y siguiendo las pautas mencionadas por otros.

  1. La simplicidad es oro, mientras más simple sea el código, menos posibilidades hay de errores o fugas
  2. Tenga cuidado al usar recursos no administrados: identificadores de Windows, conexiones de bases de datos, objetos GDI, etc.
  3. Usar usando para IDisposables
  4. Implementar IDisposable para las clases que llevan recursos no administrados
  5. Asegúrese de eliminar cualquier referencia a objetos no utilizados, incluidos los manipuladores de eventos complicados

Además de estos - implementar pruebas para ver si la memoria tiene fugas - las pruebas de unidad, concurrencia, estrés y carga podrían ayudar más aquí. Vea si hay pérdidas de memoria mediante la comprobación de métricas (contadores de rendimiento). También puede registrar creaciones y destrucciones de objetos y analizar los registros al final de una prueba.


Los tipos que implementan un finalizador pueden tener fugas, si algún finalizador se bloquea por alguna razón. He visto bloqueos de finalizadores debido a problemas de bloqueo y problemas de apartamentos.

Como las instancias de tipos con finalizadores no se recopilan hasta que se hayan ejecutado sus respectivos finalizadores, un finalizador de bloqueo único bloqueará cualquier otro objeto finalizable pendiente de recopilación.


Me encontré con problemas donde un objeto (Ping) implementó Dispose () dos veces, al implementar la interfaz IDisposable y heredarla al mismo tiempo. El método heredado no hizo nada y, como resultado, tuvo que convertir el objeto en IDisposable al llamar a Dispose () o se perdería la memoria. Aquí hay una publicación que escribí hace unos años con más detalles.


Mire que elimina cualquier controlador de eventos que use. En .NET, son la causa más común de pérdida de memoria.


No subestime la utilidad de las herramientas en estas situaciones. Los perfiladores de memoria .NET son bastante maduros y robustos hoy en día, y si tiene una aplicación complicada en la que un objeto que cree que debería liberarse todavía se mantiene como referencia por otra cosa, la capacidad de identificar esa referencia es invaluable.

Acabo de buscar una pérdida de memoria donde una página de pestañas WPF albergaba un control de Windows Form (ya que estas pestañas contenían una gran cantidad de datos y podías abrirlas y cerrarlas a voluntad simplemente esperando hasta que el GC borrara la memoria al cerrar no una opción). Usé el perfilador YourKit para tomar una instantánea de la memoria antes de que se abriera la pestaña, abrí una pestaña, la cerré de nuevo y tomé otra instantánea. Dentro del generador de perfiles, puede comparar visualmente las dos instantáneas y ver qué objetos han sobrevivido al proceso y seguir sus referencias hasta la raíz de la GC. No tengo experiencia con otros perfiladores, pero sé que hay algunos por ahí si YourKit no satisface tus necesidades.

Editado para agregar:

Está bien, esto no está evitando fugas de memoria, las está arreglando. Pero lo dejaré aquí porque creo que es información útil y no creo que los desarrolladores .NET conozcan estas herramientas.


Sé que algunas personas van a recomendar la recolección de basura como la solución. Pero hay muchos casos en que la recolección de basura no le da los resultados que espera. Es fácil terminar aferrándose a una referencia perdida que impide liberar cadenas enteras de memoria. Lea acerca de cómo esto torpedeó una entrada de DARPA Grand Challenge. Puede argumentar que estas no son pérdidas de memoria, pero si el programa espera que la memoria se libere, sigue siendo un problema. Al igual que en la programación en C, después de un par de meses se familiariza con la forma de asegurarse de no dejar atrás las referencias no deseadas.


Tener una comprensión básica de cómo funciona el recolector de basura te ayudará a evitar abusar de la memoria. Por ejemplo, si mantiene una referencia a un objeto que ya no necesita, el gc no podrá recopilarlo.

En la misma línea, si está almacenando datos que ingresa el usuario, o datos que se agregan a lo largo del tiempo, debe considerar algún tipo de limitaciones para que su uso de memoria no crezca indefinidamente.


Use la palabra clave "using" para llamar automáticamente al método Dispose () del objeto IDisposable.
Para cualquier interoperabilidad COM, debe liberar manualmente todos los recursos.


Llame a Dispose en objetos IDisposable o use la cláusula using . Eso debería encargarse de la mayoría de las filtraciones que se me ocurren.


Primero, permítanme compartir mi estricto entendimiento de una fuga de memoria. Mi definición de una pérdida de memoria es cuando tiene memoria que ha asignado y ya no es una referencia, por lo que no es posible liberar esa memoria. Habiendo dicho eso, es imposible tener una pérdida de memoria en objetos .net (me refiero a casos de tipos de CTS que viven en el montón administrado). La memoria no referenciada es precisamente lo que el GC busca para liberar.

Habiendo dicho eso, uno puede tener una comprensión más laxa de lo que es una pérdida de memoria. Si considera que una fuga de memoria es un crecimiento incontrolado de la memoria utilizada, bueno, eso es muy fácil. Simplemente haga un gran mal uso de variables estáticas, principalmente aquellas que hacen referencia a listas enormes. Si mantiene esos objetos referenciados, el GC nunca los limpiará, promocionándolos a generaciones más altas y haciéndolos aún más difíciles de recolectar. Aunque esto no es una pérdida de memoria en sentido estricto, al final del día puede provocar síntomas similares. Una buena forma de intentar detectar este tipo de "fuga" es utilizar el CLR Profiler .

Otra fuente de "filtraciones" es a través del uso incorrecto de manejadores de eventos como se mencionó anteriormente. Cada vez que ese objeto A registra uno de sus métodos de instancia con un evento en el objeto B, el objeto B finaliza manteniendo una referencia indirecta al objeto A, lo que significa que mientras B está vivo, A se mantendrá vivo. Sin embargo, tenga en cuenta que aquí no hay circularidad. Tan pronto como B o A no tengan ninguna referencia de raíz, no importa cuántas referencias cruzadas tengan, eventualmente se recopilarán.

Finalmente, uno puede realmente inducir una pérdida de memoria en .net, pero nunca (al menos teóricamente) cuando se habla de memoria administrada, ya que el GC hace un excelente trabajo al borrar eso. Si alguno de sus objetos mantiene una referencia a la memoria no administrada, por ejemplo a través de interoperabilidad, entonces esa memoria debe ser limpiada explícitamente. De lo contrario, puede provocar una pérdida de memoria. Aunque nunca he experimentado tal escenario, al menos en teoría esto puede suceder. Como se mencionó anteriormente, los objetos que contienen tales referencias deben implementar IDiposable para borrar esa memoria y su uso debe garantizar que Dispose se invoque para ese propósito, principalmente a través del uso de la cláusula using. Tenga en cuenta que Dispose no liberará la memoria del objeto, sino que solo pedirá al objeto que libere la memoria no administrada a la que se refiere.

Un tipo especial de memoria no administrada es la que necesitan los objetos COM utilizados en los escenarios de interoperabilidad. Se accede a estos objetos a través de Contenedores que pueden llamarse en tiempo de ejecución, RCW para amigos, pero no tienen Desecho. "Usar" no funcionará. La forma de liberar los objetos COM subyacentes es a través del método estático:

System.Runtime.InteropServices.Marshal.ReleaseComObject(object);

Dado que "usar" es solo azúcar de sintaxis para llamar a IDisposable.Dispose () en un bloque finally, no se puede usar con RCW, por lo tanto, no olvides colocar la llamada en ReleaseComObject (object) en un bloque finally para asegurarte de que es realmente llamado.