2008-08-01 15 views
154

En perspectiva .NET:Anatomía de una "pérdida de memoria"

  • ¿Qué es un Memory Leak?
  • ¿Cómo se puede determinar si su aplicación tiene fugas? ¿Cuáles son los efectos?
  • ¿Cómo se puede evitar una pérdida de memoria?
  • Si su aplicación tiene pérdida de memoria, ¿desaparece cuando el proceso se cierra o se cancela? ¿O las fugas de memoria en su aplicación afectan otros procesos en el sistema incluso después de la finalización del proceso?
  • ¿Y qué pasa con el código no administrado al que se accede a través de COM Interop y/o P/Invoke?

Respuesta

103

La mejor explicación que he visto es en el Capítulo 7 de la Foundations of Programming ebook gratuita.

Básicamente, en .NET se produce una pérdida de memoria cuando los objetos a los que se hace referencia son rooteados y, por lo tanto, no se pueden recolectar basura. Esto ocurre accidentalmente cuando se aferra a referencias más allá del alcance deseado.

Sabrá que tiene fugas cuando comience a salir de las mejores excepciones o si su uso de memoria aumenta más de lo que cabría esperar (perfmon tiene buenos contadores de memoria).

Comprender el modelo de memoria de .NET es su mejor forma de evitarlo. Específicamente, entender cómo funciona el recolector de basura y cómo funcionan las referencias (de nuevo, lo remito al capítulo 7 del libro electrónico). Además, tenga en cuenta las trampas comunes, probablemente los eventos más comunes. Si el objeto A registrado en un evento en el objeto B, entonces el objeto A se mantendrá hasta que el objeto B desaparezca porque B contiene una referencia a A. La solución es anular el registro de sus eventos cuando haya terminado.

Por supuesto, un buen perfil de memoria le permitirá ver sus gráficos de objetos y explorar el anidamiento/referencias de los objetos para ver donde las referencias provienen y qué objeto raíz es responsable (red-gate ants profile, JetBrains dotMemory, memprofiler son muy buenos opciones, o puede usar windbg y sos solo de texto, pero recomendaría encarecidamente un producto comercial/visual a menos que sea un verdadero gurú).

Creo que el código no administrado está sujeto a fugas de memoria típicas de código no procesado, excepto que las referencias compartidas entre las dos son administradas por el recolector de elementos no utilizados. Podría estar equivocado sobre este último punto.

+11

Oh, te gusta el libro, ¿o sí? He visto al autor aparecer en stackoverflow de vez en cuando. –

+0

Algunos objetos .NET también pueden rootearse y volverse incobrables. Todo lo que es IDisposable debe ser eliminado debido a esto. – kyoryu

+1

@kyoryu: ¿Cómo se enraíza un objeto? –

13

Supongo que en un entorno administrado, una fuga sería mantener una referencia innecesaria a un gran trozo de memoria.

8

Coincidiré con Bernard en que en .net será una fuga de mem.

Puede perfilar su aplicación para ver su uso de memoria, y determinar que si está administrando mucha memoria cuando no debería estar podría decir que tiene una fuga.

En términos manejados pondré mi cuello en la línea para decir que desaparece una vez que el proceso se cancela/elimina.

El código no administrado es su propia bestia y si existe una fuga dentro de ella, seguirá una memoria estándar. definición de fuga

31

Estrictamente hablando, una pérdida de memoria consume memoria que "ya no usa" el programa.

"Ya no se usa" tiene más de un significado, podría significar "no más referencia", es decir, totalmente irrecuperable, o podría significar, referenciado, recuperable, sin usar, pero el programa conserva las referencias de todos modos. Solo lo posterior se aplica a .Net para objetos perfectamente administrados. Sin embargo, no todas las clases son perfectas y en algún punto una implementación subyacente no administrada podría perder recursos permanentemente para ese proceso.

En todos los casos, la aplicación consume más memoria de la estrictamente necesaria. Los efectos secundarios, dependiendo de la cantidad filtrada, podrían ir desde ninguno, a la desaceleración causada por la recolección excesiva, a una serie de excepciones de memoria y finalmente a un error fatal seguido de la terminación forzada del proceso.

Sabe que una aplicación tiene un problema de memoria cuando la supervisión muestra que se asigna más y más memoria a su proceso después de cada ciclo de recolección de basura. En tal caso, está guardando demasiado en memoria o está filtrando alguna implementación subyacente no administrada.

Para la mayoría de las filtraciones, los recursos se recuperan cuando finaliza el proceso; sin embargo, algunos recursos no siempre se recuperan en algunos casos precisos, los controladores de cursor GDI son notorios para eso. Por supuesto, si tiene un mecanismo de comunicación entre procesos, la memoria asignada en el otro proceso no se liberará hasta que ese proceso la libere o finalice.

17

Definiría las pérdidas de memoria como un objeto que no libera toda la memoria asignada después de que se haya completado. He encontrado que esto puede suceder en su aplicación si usa la API de Windows y COM (es decir, código no administrado que tiene un error o no se está administrando correctamente), en el marco y en componentes de terceros. También descubrí que no hacer tic-tac después de usar ciertos objetos, como las plumas, puede causar el problema.

Personalmente he sufrido excepciones de falta de memoria que pueden ser causadas pero que no son exclusivas de las fugas de memoria en las aplicaciones dot net. (OOM también puede provenir de fijar vea Pinning Artical). Si no está recibiendo errores OOM o necesita confirmar si se trata de una pérdida de memoria, entonces la única forma es perfilar su aplicación.

Me gustaría también tratar de asegurarse de lo siguiente:

a) Todo lo que implementa IDisposable está dispuesto ya sea usando un bloque finally o la instrucción using estos incluyen pinceles, lápices, etc. (algunas personas argumentan para configurar todo a la nada Además)

b) Cualquier cosa que tiene un método de cierre se cierra de nuevo utilizando fin o la instrucción using (aunque he encontrado utilizando no siempre cerca dependiendo de si usted declaró el objeto fuera de la instrucción using)

c) Si está usando código no administrado/API de Windows que estos son alt con correctamente después. (algunos tienen métodos de limpieza para liberar recursos)

Espero que esto ayude.

8

Todas las pérdidas de memoria se resuelven por la terminación del programa.

Perder la memoria suficiente y el sistema operativo puede decidir resolver el problema en su nombre.

9

supongo que en un entorno administrado, una fuga habría que mantener un referencia innecesaria a una gran parte de la memoria alrededor.

Absolutamente. Además, no usar el método .Dispose() en objetos desechables cuando sea apropiado puede causar pérdidas de mem. La forma más sencilla de hacerlo es con el uso de un bloque, ya que se ejecuta automáticamente .Dispose() al final:

StreamReader sr; 
using(sr = new StreamReader("somefile.txt")) 
{ 
    //do some stuff 
} 

Y si crea una clase que está utilizando objetos no administrados, si no está correctamente implementar IDisposable , podría estar causando pérdidas de memoria para los usuarios de su clase.

6

También tenga en cuenta que .NET tiene dos montones, uno es el montón de objetos grandes. Creo que los objetos de aproximadamente 85 k o más se ponen en este montón. Este montón tiene reglas de vida diferentes a las del montón normal.

Si está creando estructuras de memoria de gran tamaño (de diccionario o de lista) sería prudente ir a buscar cuáles son las reglas exactas.

En cuanto a la recuperación de la memoria en la terminación del proceso, a menos que ejecute Win98 o sus equivalentes, todo se libera de nuevo en el sistema operativo en la terminación. Las únicas excepciones son las cosas que se abren en el proceso cruzado y otro proceso todavía tiene el recurso abierto.

COM Los objetos pueden ser difíciles aunque. Si siempre usa el patrón IDispose, estará seguro. Pero me he encontrado con algunos ensambles de interoperabilidad que implementan IDispose. La clave aquí es llamar al Marshal.ReleaseCOMObject cuando haya terminado con esto. Los objetos COM todavía usan el conteo de referencias COM estándar.

17

Si necesita para diagnosticar una pérdida de memoria en .NET, comprobar estos enlaces:

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

Estos artículos describen cómo crear un volcado de memoria de su proceso y cómo analizar para que pueda determinar primero si su fuga no está administrada o administrada, y si se gestiona, cómo averiguar de dónde viene.

Microsoft también tiene una herramienta más nueva para ayudar a generar volcados de memoria, para reemplazar ADPlus, llamada DebugDiag.

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en

27

creo que el "lo que es una pérdida de memoria" y "¿cuáles son los efectos de" preguntas han sido contestadas bien ya, pero quería añadir algunas cosas más sobre las demás cuestiones ...

¿Cómo entender si sus fugas de aplicación

Una forma interesante es abrir Monitor de rendimiento y añadir trazas de # bytes en todos los montones y # Gen 2 colecciones, en cada caso buscando solo su proceso. Si el ejercicio de una característica particular provoca que aumenten los bytes totales, y que la memoria permanezca asignada después de la próxima colección de Gen 2, puede decir que la función pierde memoria.

Cómo prevenir

Otras buenas opiniones se han dado.Simplemente agregaría que quizás la más comúnmente olvidada causa de fugas de memoria .NET es agregar controladores de eventos a los objetos sin eliminarlos. Un controlador de eventos adjunto a un objeto es una forma de referencia para ese objeto, por lo que evitará la recopilación incluso después de que todas las demás referencias hayan desaparecido. Recuerde siempre separar los manejadores de eventos (usando la sintaxis -= en C#).

¿La fuga desaparece cuando el proceso finaliza, y qué pasa con la interoperabilidad COM?

Cuando se cierra el proceso, el SO recupera toda la memoria asignada en su espacio de direcciones, incluidos los objetos COM servidos desde DLL. Comparativamente raramente, los objetos COM pueden ser servidos desde procesos separados. En este caso, cuando finaliza su proceso, puede seguir siendo responsable de la memoria asignada en cualquier proceso de servidor COM que haya utilizado.

5

Encontré .Net Memory Profiler una muy buena ayuda al encontrar pérdidas de memoria en .Net. No es gratis como Microsoft CLR Profiler, pero es más rápido y más directo en mi opinión. A

13

La mejor explicación de cómo funciona el recolector de basura está en Jeff Richters CLR via C# book, (Ch. 20). Leer esto proporciona una gran base para comprender cómo persisten los objetos.

Una de las causas más comunes de enraizamiento de objetos accidentalmente es mediante la conexión de eventos a una clase. Si conecta un evento externo

p.

SomeExternalClass.Changed += new EventHandler(HandleIt); 

y olvidar para desenganchar a ella cuando se deshaga, a continuación, SomeExternalClass tiene una nevera a su clase.

Como se mencionó anteriormente, el SciTech memory profiler es excelente para mostrarle las raíces de los objetos que sospecha tienen fugas.

Pero hay también una manera muy rápida de comprobar un determinado tipo se acaba de utilizar WnDBG (incluso se puede utilizar esto en la ventana inmediata VS.NET mientras que está unido):

.loadby sos mscorwks 
!dumpheap -stat -type <TypeName> 

Ahora hacer algo que piense que eliminará los objetos de ese tipo (por ejemplo, cerrar una ventana). Aquí es útil tener un botón de depuración en algún lugar que ejecutará System.GC.Collect() un par de veces.

Ejecute !dumpheap -stat -type <TypeName> nuevamente. Si el número no disminuyó, o no bajó tanto como esperabas, entonces tienes una base para una mayor investigación. (recibí este consejo de un seminario dado por Ingo Rammer).

9

¿Por qué la gente piensa que una pérdida de memoria en .NET no es igual que cualquier otra pérdida?

Una pérdida de memoria es cuando se conecta a un recurso y no lo suelta. Puede hacerlo tanto en codificación administrada como no administrada.

En cuanto a .NET y otras herramientas de programación, ha habido ideas sobre la recolección de basura y otras formas de minimizar las situaciones que harán que su aplicación se escape. Pero el mejor método para evitar fugas de memoria es que necesita comprender su modelo de memoria subyacente y cómo funcionan las cosas en la plataforma que está utilizando.

Creer que el GC y otras magias limpiarán su desorden es el camino corto a las pérdidas de memoria, y será difícil de encontrar más adelante.

Al codificar no administrado, normalmente asegúrese de limpiar, usted sabe que los recursos que se apoderan de usted serán su responsabilidad de limpiar, no del conserje.

En .NET, por otro lado, mucha gente piensa que el GC lo limpiará todo. Bueno, hace algo por ti, pero debes asegurarte de que sea así. .NET envuelve muchas cosas, por lo que no siempre se sabe si se trata de un recurso gestionado o no gestionado, y debe asegurarse de qué está tratando. El manejo de fuentes, recursos de GDI, directorio activo, bases de datos, etc. suele ser algo que debe tener en cuenta.

En términos administrados Pondré mi cuello en la línea decir que no desaparezca una vez el proceso se mata/eliminado.

Veo que mucha gente tiene esto sin embargo, y realmente espero que esto termine. ¡No puedes pedirle al usuario que finalice tu aplicación para limpiar tu desorden! Eche un vistazo a un navegador, que puede ser IE, FF, etc., luego abra, por ejemplo, Google Reader, déjelo permanecer unos días y observe lo que sucede.

Si luego abre otra pestaña en el navegador, navega hacia algún sitio, luego cierra la pestaña que albergaba la otra página que hizo que el navegador se filtre, ¿cree que el navegador liberará la memoria? No es así con IE. En mi computadora IE fácilmente comerá 1 GiB de memoria en un corto período de tiempo (alrededor de 3-4 días) si utilizo Google Reader. Algunas páginas de noticias son aún peores.