2009-11-13 14 views
7

Supongo que esto es muy básico, pero dado que estoy aprendiendo .NET solo, tengo que hacer esta pregunta.Pregunta sobre el recolector de basura en .NET (pérdida de memoria)

Estoy acostumbrado a la codificación en C, donde usted tiene que free() todo. En C++ /. NET, he leído sobre el recolector de basura. Por lo que entiendo, cuando una instancia ya no se usa (en el alcance del objeto), es liberada por el recolector de basura.

lo tanto, tener esto en mente, he construido una pequeña aplicación de prueba. Pero, parece que no obtuve algo porque al hacer las mismas cosas algunas veces (como abrir un formulario, cerrarlo, volver a abrirlo, etc.), la memoria se filtra. Y a lo grande.

Traté de mirar esto en Google, pero no encontré nada bueno para un principiante.

  1. es el colector de basura realmente liberador cada objetos cuando ya no se utiliza o hay excepciones que tengo que manejar? ¿Qué me estoy perdiendo?
  2. ¿Hay herramientas gratuitas para buscar fugas de memoria?
+7

¿Cómo sabe que la memoria tiene una fuga? – Dmitry

+0

Estaba siguiendo la memoria en el Administrador de tareas. Ver los otros dos comentarios que hice a continuación. Tal vez no es una "fuga" pero es molesto, no obstante. – Procule

+2

@Procule - Te preocupes si el CLR GC funciona. Lo hace. Muchos codificadores C/C++ lo usan y están muy contentos con él. Sin embargo, debe aprender sobre el uso de/IDisposable pattern, y verificar los documentos y ver si un objeto implementa IDisposable. Pero la gran ventaja de .NET no es microgestionar este tipo de cosas. – overslacked

Respuesta

11

Sí, el recolector de basura está liberando sus objetos cuando no se usan más.

lo que usualmente llamamos una pérdida de memoria en .NET es más como:

  • Usted está utilizando recursos externos (que no son basura recogida) y el olvido para liberarlos. Esto se resuelve generalmente implementando la interfaz IDisposable.
  • le parece que no son referencias a un objeto dado, pero de hecho hay, en algún lugar y no se están usando más sobre ellos, pero el recolector de basura no sabe nada de ellos.

Además, la memoria solo se recupera cuando es necesario, lo que significa que el recolector de basura se activa en momentos determinados y realiza una recopilación que determina qué memoria se puede liberar y liberar. Hasta que lo haga, la memoria no se reclama, por lo que podría parecer que la memoria se está perdiendo.

aquí, creo que voy a ofrecer una respuesta más compleja sólo para aclarar.

Primero, el recolector de basura se ejecuta en su propio hilo.Debe comprender que, para recuperar memoria, el recolector de basura necesita detener todos los otros hilos para poder seguir la cadena de referencia y determinar qué depende de qué. Esa es la razón por la que la memoria no se libera de inmediato, un recolector de basura implica ciertos costos en el rendimiento.

Internamente, el recolector de basura administra la memoria en generaciones. De manera muy simplificada, hay varias generaciones para objetos de larga vida, vida corta y gran tamaño. El recolector de basura mueve el objeto de una generación a otra cada vez que realiza una colección, que ocurre más a menudo para la generación efímera que la de larga duración. También reasigna objetos para obtener el espacio contiguo más posible, así que de nuevo, realizar una colección es un proceso costoso.

Si realmente desea ver los efectos de liberar el formulario (al salir del alcance y sin tener más referencias) puede llamar al GC.Collect(). Haga eso solo para probar, es muy imprudente llamar a Collect, excepto en unos pocos casos donde sabe exactamente lo que está haciendo y las implicaciones que tendrá.

Un poco más de explicación sobre el método Dispose de la interfaz IDispose.

Dispose no es un destructor en la forma habitual de C++, no es un destructor en absoluto. Dispose es una forma de eliminar deterministamente los objetos no administrados. Un ejemplo: suponga que llama a una biblioteca COM externa que asigna 1GB de memoria debido a lo que está haciendo. Si no tiene Dispose, la memoria se quedará allí, desperdiciando espacio hasta que el GC inice una colección y recupere la memoria no administrada llamando al destructor de objetos real. Entonces, si desea liberar la memoria de inmediato, debe llamar al método Dispose, pero no está "forzado" a hacerlo.

Si no utiliza la interfaz IDisposable, debe liberar sus recursos no administrados en el método Finalize. Finalize se llama automáticamente desde el destructor de objetos cuando el GC intenta reclamar el objeto. Entonces, si tiene un método de finalización adecuado, la memoria no administrada se liberará de cualquier forma. Calling Dispose solo lo hará determinista.

+0

(primer botón) ¿Quiere decir un objeto no administrado? (segundo botón) Pareces decir las mismas cosas que los demás. Por lo tanto, tal vez el objeto todavía se hace referencia, pero por un objeto que no sé. ¿Hay alguna forma de saber? Porque el depurador (en VS2008) solo le informa sobre los locales. (Estoy acostumbrado a codificar en C/Linux) – Procule

+0

@Jorge: +1, excelente explicación del GC y las generaciones. (Lo cual es muy útil para alguien que acaba de ingresar a .NET, y lamentablemente mi experiencia ha demostrado uno de los elementos menos enseñados). –

+1

Sí, quiero decir objetos no administrados en general, pero no siempre los obvios. Por ejemplo, un puntero fijo inseguro podría hacer que un objeto quede inmovilizado y detener el objeto de referencia para que se reubique, lo que a su vez podría provocar la fragmentación de la memoria y, a su vez, podría generar un gran "espacio perdido" –

3

El GC no necesariamente liberará las cosas en el momento en que ya no se haga referencia al objeto. El GC lo recogerá en algún momento en el futuro; no sabe exactamente cuándo, pero si se necesita memoria, el GC realizará una recopilación si es necesario.

+0

Eso es algo que descubrí. Mientras miraba al administrador de tareas, después de algunas veces se liberaron algunos recuerdos. Pero, cuando se inicia el programa, toma 20 megas, pasa a 80 megas y luego baja a 60 megas. Eso sigue siendo 40 megas y se supone que debo estar en el mismo "estado". – Procule

8

¿Qué te lleva a concluir que hay pérdidas de memoria? En la recolección de elementos no utilizados, no hay garantía de que la memoria se libere inmediatamente, y, en general, el GC no entra en funcionamiento hasta que la memoria de proceso alcance algún umbral, o el montón disponible se haya agotado. (La heurística exacta es complicada y no es importante). Por lo tanto, el hecho de que la memoria de su proceso aumente y no disminuya no significa necesariamente que haya un error. Puede ser que el GC no haya podido limpiar tu proceso todavía.

Además, ¿está seguro que no hay referencias a sus objetos? Es posible que tenga referencias de las que no tenga conocimiento. La mayoría de las pérdidas de memoria en las aplicaciones .NET se deben a que las personas no se dan cuenta de que su memoria aún se referencia en algún lugar.

+0

Entiendo tu punto. (ver el primer comentario a "Michael Burr"). Pero pasar de 20 a 80 megas ... parece un poco alto para mí. ¿Debo eliminar() el objeto manualmente? – Procule

+0

¿Qué quiere 'Dispose()'? En general, la mayoría de los objetos que implementan la interfaz 'IDisposable' deben eliminarse. No estoy seguro acerca de C++/CLR, pero en C# a menudo se hace con la construcción 'using'. Sin embargo, en el caso de los objetos de forma, la vida es un poco diferente. Y entiendo que el CLR en sí tiene una huella medianamente pesada, y también que el informe de memoria que ve en el Administrador de tareas es muy posiblemente inexacto. (Utilice ANTS u otro buen generador de perfiles para asegurarse de que realmente tiene una fuga). –

+0

Gracias, parece que responde mi segunda pregunta. Entonces, "ANTS" es un programa que me ayuda a administrar/mirar la memoria? (por cierto, la mitad de mi código está en C++/CLI y la otra mitad en C# (Cambié a mitad de camino, parece que C# es mucho más fácil!)) – Procule

0

Hay herramientas gratuitas disponibles para mirar el montón administrado en .Net, utilizando las extensiones a SOSEXWinDBG y SOS, es posible ejecutar un programa, hacer una pausa, a continuación, busque en la que los objetos existen en la pila y (más importante aún) qué otros objetos tienen referencias a ellos (raíces) lo que impedirá que el GC los recoja.

4

estoy añadiendo esto como una respuesta en lugar de un comentario sobre la cuestión, pero esto sigue en marcha una pregunta formulada por el PO en un comentario: using comunicado en MSDN. IDisposable interfaz en MSDN.

Aquí está el quid de la cuestión: a lo que estás acostumbrado en cuanto a los objetos destructores se ha ido. Todo lo que le han dicho sobre cómo codificar correctamente está gritando desde su subconsciente, diciendo que esto no puede ser cierto, y si es cierto, es incorrecto y terrible. Es muy diferente; es difícil recordar lo mucho que realmente lo desprecié al principio (era un orgulloso desarrollador de C++).

Yo personalmente le prometo: ¡todo va a estar bien!

Aquí hay otra cosa buena para leer: Destructors and Finalizers in Visual C++.

+1

jaja me gusta cómo dices eso. Te haría +1 pero necesito 15 puntos para hacer eso. Gracias ! – Procule

7

Administrador de tareas es una manera terrible de examinar el uso de la memoria. Si desea estudiar cómo funciona el recolector de basura, instale el CLR Profiler y úselo para analizar su aplicación. Le dirá exactamente lo que está haciendo el recolector de basura.

Ver How To: Use CLR Profiler.

+0

Gracias por el consejo, estoy buscando en CLR Profiler. +1 – Procule

+0

Me gustaría ir con perfmon primero, antes de sumergirme más profundo con windbg o clr profiler, ya que es mucho más liviano y tiene un montón de contadores como tamaño de pila LOB, tamaño de pila Gen2, recuentos de colección, etc. También CLR profiler usa ICorProfiler que no admite adjuntar, por lo que debe tomar la decisión de realizar un perfil por adelantado –

1

Si lo que desea es averiguar si usted tiene una pérdida de memoria o no, echar un vistazo a perfmon que se suministra con su copia de Windows:

En particular, el contador Memoria de .NET CLR bytes in all heaps, si este número está creciendo constantemente tienes una fuga.

Incluso puede cavar más profundo comparando el tamaño del montón Gen 2 al gran tamaño del montón de objetos. Si el primero está creciendo constantemente, tiene una gran cantidad de datos que se escapan.

Una vez que confirme que hay una fuga, se puede adjuntar con windbg y utilizar el sos extensions para determinar lo que se está escapando.

Si usted puede permitirse el lujo de gastar unos cuantos dólares echar un vistazo a la .NET Memory Profiler.

Cuestiones relacionadas