Fuga de memoria en C #

¿Es posible en algún momento que un sistema administrado pierda memoria cuando se asegura de que todos los identificadores, elementos que implementan IDispose están eliminados?

¿Habría casos en que algunas variables se omiten?

Los controladores de eventos son una fuente muy común de pérdidas de memoria no obvias. Si se suscribe a un evento en object1 desde object2, luego object2.Dispose () y finge que no existe (y omite todas las referencias de su código), hay una referencia implícita en el evento de object1 que impedirá que object2 basura recolectada.

 MyType object2 = new MyType(); // ... object1.SomeEvent += object2.myEventHandler; // ... // Should call this // object1.SomeEvent -= object2.myEventHandler; object2.Dispose(); 

Este es un caso común de una fuga: olvidarse de darse de baja fácilmente de los eventos. Por supuesto, si object1 se recostack, object2 también se recostackrá, pero no hasta entonces.

No creo que las memory leaks de estilo C ++ sean posibles. El recolector de basura debería dar cuenta de eso. Es posible crear un objeto estático que agregue referencias a objetos aunque los objetos nunca vuelvan a utilizarse. Algo como esto:

 public static class SomethingFactory { private static List listOfSomethings = new List(); public static Something CreateSomething() { var something = new Something(); listOfSomethings.Add(something); return something; } } 

Es un ejemplo obviamente estúpido, pero sería el equivalente de una fuga de memoria de tiempo de ejecución administrado.

Como han señalado otros, siempre que no haya un error real en el administrador de memoria, las clases que no usan recursos no administrados no perderán memoria.

Lo que ves en .NET no son pérdidas de memoria, sino objetos que nunca se eliminan. Un objeto no se eliminará mientras el recolector de basura pueda encontrarlo en el gráfico de objetos. Entonces, si algún objeto vivo en alguna parte tiene una referencia al objeto, no será eliminado.

El registro de eventos es una buena forma de hacer que esto suceda. Si un objeto se registra para un evento, cualquier cosa que tenga registrada tiene una referencia e incluso si elimina cualquier otra referencia al objeto, hasta que se anule el registro (o el objeto con el que se registró se vuelva inalcanzable) seguirá vivo.

Por lo tanto, debe tener cuidado con los objetos que se registran para eventos estáticos sin su conocimiento. Una característica ingeniosa de la ToolStrip , por ejemplo, es que si cambia el tema de la pantalla, se volverá a mostrar automáticamente en el nuevo tema. SystemEvents.UserPreferenceChanged esta función ingeniosa registrándose para el evento estático SystemEvents.UserPreferenceChanged . Cuando cambia el tema de Windows, el evento se eleva y todos los objetos ToolStrip que están escuchando el evento reciben una notificación de que hay un nuevo tema.

De acuerdo, supongamos que decides tirar un ToolStrip en tu formulario:

 private void DiscardMyToolstrip() { Controls.Remove("MyToolStrip"); } 

Ahora tiene una ToolStrip que nunca morirá. A pesar de que ya no está en su formulario, cada vez que el usuario cambie de tema, Windows diligentemente le dirá al ToolStrip inexistente al respecto. Cada vez que se ejecuta el recolector de elementos no utilizados, cree que “no puedo tirar ese objeto, el evento UserPreferenceChanged está usando”.

Eso no es una pérdida de memoria. Pero podría ser así.

Cosas como esta hacen que un perfilador de memoria sea invaluable. Ejecute un generador de perfiles de memoria y dirá “es extraño, parece que hay diez mil objetos ToolStrip en el montón, aunque solo haya uno en mi formulario. ¿Cómo sucedió esto?”

Ah, y en caso de que se pregunte por qué algunas personas piensan que los emisores de propiedades son malvados: para obtener una ToolStrip para anular el UserPreferenceChanged evento UserPreferenceChanged , establezca su propiedad Visible en false .

Los delegates pueden provocar memory leaks poco intuitivas.

Cada vez que crea un delegado a partir de un método de instancia, una referencia a esa instancia se almacena “en” ese delegado.

Además, si combina varios delegates en un delegado de multidifusión, tiene una gran cantidad de referencias a numerosos objetos que se guardan de la recolección de basura, siempre que ese delegado de multidifusión se esté utilizando en alguna parte.

Si está desarrollando una aplicación WinForms, una “fuga” sutil es la propiedad Control.AllowDrop (utilizada para habilitar Arrastrar y soltar). Si AllowDrop está establecido en “verdadero”, el CLR aún se mantendrá en su control a través de System.Windows.Forms.DropTarget . Para solucionar esto, asegúrese de que la propiedad AllowDrop su Control esté configurada en false cuando ya no la necesite y el CLR se encargará del rest.

Como ya se mencionó, el mantenimiento de referencias dará lugar a un aumento en el uso de la memoria a lo largo del tiempo. Una manera fácil de entrar en esta situación es con eventos. Si tenía un objeto viviente largo con algún evento que sus otros objetos escuchan, si los oyentes nunca se eliminan, entonces el evento en el objeto de larga vida mantendrá esas otras instancias con vida mucho después de que ya no se necesiten.

La única razón para la pérdida de memoria en la aplicación .NET es que todavía se hace referencia a los objetos aunque su duración haya finalizado. Por lo tanto, el recolector de basura no puede recogerlos. Y se convierten en objetos de larga vida.

Encuentro que es muy fácil causar pérdidas suscribiéndote a eventos sin anular la suscripción cuando termina la vida del objeto.

Puede encontrar mi nuevo artículo útil: Cómo detectar y evitar pérdidas de memoria y recursos en aplicaciones .NET

La emisión de reflexión es otra fuente potencial de fugas, por ejemplo, deserializadores de objetos incorporados y sofisticados clientes SOAP / XML. Al menos en versiones anteriores del marco, el código generado en AppDomains dependientes nunca se descargó …

Es un mito que no se puede perder memoria en el código administrado. Por supuesto, es mucho más difícil que en C ++ no administrado, pero hay un millón de formas de hacerlo. Objetos estáticos que contienen referencias, referencias innecesarias, almacenamiento en caché, etc. Si está haciendo las cosas de la manera “correcta”, muchos de sus objetos no recogerán basura hasta mucho más tarde de lo necesario, que es una especie de pérdida de memoria también en mi opinión, de una manera práctica y no teórica.

Afortunadamente, hay herramientas que pueden ayudarte. Utilizo mucho CLR Profiler de Microsoft: no es la herramienta más fácil de usar jamás escrita, pero definitivamente es muy útil y gratuita.

Una vez que todas las referencias a un objeto hayan desaparecido, el recolector de basura liberará ese objeto en su siguiente paso. No diría que es imposible perder la memoria, pero es bastante difícil, para filtrar hay que hacer referencia a un objeto sentado sin darse cuenta.

Por ejemplo, si crea una instancia de los objetos en una lista y luego olvida eliminarlos de la lista cuando termine Y olvide deshacerse de ellos.

Es posible tener fugas si los recursos no administrados no se limpian adecuadamente. Las clases que implementan IDisposable pueden tener fugas.

Sin embargo, las referencias a objetos regulares no requieren una gestión de memoria explícita de la misma forma que los lenguajes de nivel inferior.

No es realmente una pérdida de memoria, pero es bastante fácil quedarse sin memoria cuando se usan objetos grandes (más de 64K si no recuerdo mal). Se almacenan en LOH y NO se desfragmentan. Entonces, usar esos objetos grandes y liberarlos libera la memoria en el LOH, pero el tiempo de ejecución de .NET ya no usa esa memoria libre para este proceso. De modo que puede quedarse sin espacio en el LOH usando solo unos pocos objetos grandes en el LOH. Este problema es conocido por Microsoft, pero, como recuerdo, ahora se está planificando una solución para esto.

Las únicas fugas (aparte de errores en el tiempo de ejecución que pueden estar presentes, aunque no muy probablemente debido a la recolección de basura) van a ser para recursos nativos. Si P / Invoque en una biblioteca nativa que abre identificadores de archivo, o conexiones de socket, o lo que sea en nombre de su aplicación administrada, y nunca los cierra explícitamente (y no los maneja en un triturador o destructor / finalizador), puede tiene memory leaks o de recursos porque el tiempo de ejecución no puede administrar todos esos automáticamente para usted.

Sin embargo, si se queda con recursos puramente administrados, debería estar bien. Si experimenta alguna forma de pérdida de memoria sin llamar al código nativo, entonces eso es un error.

Si bien es posible que algo en el marco tenga una fuga, más probable es que tengas algo que no se está desechando correctamente o algo está impidiendo que el GC se deshaga de él, IIS sería un candidato ideal para esto.

Solo recuerde que no todo en .NET es código totalmente administrado, interoperabilidad COM, archivo io como flujos de archivos, solicitudes DB, imágenes, etc.

Un problema que tuvimos hace un tiempo (.net 2.0 en IIS 6) fue que creamos una imagen y luego nos deshacemos de ella, pero IIS no liberaría la memoria por un tiempo.

En mi último trabajo, estábamos usando una biblioteca .NET SQLite de terceros que se filtró como un colador.

Estábamos haciendo un montón de insertos de datos rápidos en una situación extraña donde la conexión de la base de datos tenía que abrirse y cerrarse cada vez. La lib de terceros hizo algo de la misma apertura de conexión que se suponía que debíamos hacer manualmente y no lo documentó. También mantuvo las referencias en algún lugar que nunca encontramos. El resultado fue 2x tantas conexiones se abrieron como se suponía que debían ser y solo 1/2 se cerraron. Y dado que las referencias se llevaron a cabo, tuvimos una pérdida de memoria.

Obviamente, esto no es lo mismo que una pérdida de memoria C / C ++ clásica, pero para todos los efectos fue uno para nosotros.

Si se considera pérdida de memoria, también se puede lograr con este tipo de código:

 public class A { B b; public A(B b) { this.b = b; } ~A() { b = new B(); } } public class B { A a; public B() { this.a = new A(this); } ~B() { a = new A(this); } } class Program { static void Main(string[] args) { { B[] toBeLost = new B[100000000]; foreach (var c in toBeLost) { toBeLost.ToString(); //to make JIT compiler run the instantiation above } } Console.ReadLine(); } } 

Las funciones pequeñas ayudan a evitar “pérdidas de memoria”. Porque el recolector de basura libera variables locales al final de las funciones. Si la función es grande y requiere mucha memoria, usted mismo tiene que liberar variables locales que requieren mucha memoria y ya no son necesarias. Las variables globales similares (matrices, listas) también son malas.

Experimenté pérdidas de memoria en C # al crear imágenes y no eliminarlas. Lo cual es un poco extraño. La gente dice que debes llamar a .Dispose () en cada objeto que lo tiene. Pero la documentación para las funciones gráficas C # no siempre menciona esto, por ejemplo para la función GetThumbnailImage (). El comstackdor de C # debería advertirte sobre esto, creo.

Un recordatorio sobre cómo encontrar la fuga de memoria:

  • Eliminar y gc.collect llamadas.
  • Espere hasta que tengamos la certeza de que la memoria está goteando.
  • Crear archivo de volcado desde el administrador de tareas.
  • Abra los archivos de volcado usando DebugDiag .
  • Analiza el resultado primero. El resultado de allí debería ayudarnos, lo que normalmente requiere la mayor parte de la memoria.
  • Repare el código hasta que no se encuentre ninguna pérdida de memoria allí.
  • Use una aplicación de terceros, como el generador de perfiles .net. (Podemos usar prueba, pero necesitamos resolver el problema lo antes posible. El primer volcado debería ayudarnos sobre todo acerca de cómo se filtró)
  • Si el problema está en la memoria virtual, necesita ver la memoria no administrada. (Por lo general, hay otra configuración que debe habilitarse)
  • Ejecute una aplicación de terceros según cómo se usa.

Problema de fuga de memoria común:

  • Eventos / delegates nunca se elimina. (Cuando deseche, asegúrese de que el evento no esté registrado) – vea la respuesta de ReepChopsey
  • List / Dictionary nunca fueron borrados.
  • Objeto referenciado a otro objeto que se guarda en la memoria nunca será eliminado. (Clonarlo para una administración más fácil)