33

Estoy tratando de ejecutar una pérdida de memoria en una aplicación de formularios de Windows. Estoy buscando ahora un formulario que contiene varias formas incrustadas. Lo que me preocupa es que los formularios secundarios, en su constructor, hagan referencia al formulario principal y lo mantengan en un campo privado. Entonces me parece que viene el tiempo de recolección de basura:Referencias circulares ¿Causa pérdida de memoria?

El padre tiene una referencia al formulario secundario, a través de la colección de controles (el formulario hijo está incrustado allí). La forma del niño no es GC'd.

El formulario hijo tiene una referencia al formulario padre, a través del campo de miembro privado. El formulario principal no es GC.

¿Es esto una comprensión precisa de cómo el recolector de basura evaluará la situación? ¿Alguna forma de "probarlo" para fines de prueba?

Respuesta

36

¡Una gran pregunta!

No, Ambas formas serán (pueden) GC'd porque el GC no busca directamente referencias en otras referencias. Solo busca lo que se llama referencias "Root" ... Esto incluye variables de referencia en la pila, (La variable está en la pila, el objeto real está, por supuesto, en el montón), hace referencia a las variables en los registros de la CPU y las variables de referencia que están campos estáticos en clases ...

Todas las demás variables de referencia solo son accedidas (y GC'd) si se referencian en una propiedad de uno de los objetos de referencia "raíz" encontrados por el proceso anterior ... (o en un objeto al que hace referencia una referencia en un objeto raíz, etc. ...)

Solo si uno de los formularios se referencia en otro lugar en una referencia "raíz" - Entonces ambos formularios estarán a salvo del GC.

única manera en que puedo pensar para "probarlo", (sin utilizar utilidades de seguimiento de memoria) sería crear cientos cientos de miles de estas formas, en un ciclo dentro de un método, luego, mientras está en el método, mire la huella de memoria de la aplicación, luego salga del método, llame al GC y mire nuevamente la huella.

+2

O simplemente asigne un buffer masivo dentro de cada formulario. – Gusdor

5

Si no se hace referencia al padre y al hijo, sino que solo se referencian entre ellos, obtienen GCed.

Obtenga un generador de perfiles de memoria para comprobar realmente su aplicación y responder a todas sus preguntas. Puedo recomendar http://memprofiler.com/

0

El GC puede tratar correctamente con referencias circulares y si estas referencias fueran lo único que mantuviera el formulario vivo, entonces se recopilarían.
He tenido muchos problemas con .net al no recuperar memoria de formularios. En 1.1 hubo algunos errores alrededor de menuitem (creo) lo que significaba que no se eliminaban y podían perder memoria. En este caso, al agregar una llamada explícita para eliminar y borrar la variable miembro en el método Dispose del formulario, se solucionó el problema. Descubrimos que esto también ayudó a reclamar memoria para algunos de los otros tipos de control.
También pasé mucho tiempo con el perfilador de CLR mirando por qué los formularios no se estaban recopilando. Por lo que pude ver, el marco estaba manteniendo las referencias. Uno por tipo de formulario. Entonces, si crea 100 instancias de Form1, entonces ciérrelas todas, solo 99 se recuperarán correctamente. No encontré ninguna manera de curar esto.
Nuestra aplicación se ha movido a .net 2 y esto parece ser mucho mejor. La memoria de nuestra aplicación aún aumenta cuando abrimos el primer formulario y no retrocede cuando está cerrado, pero creo que esto se debe al código JIT y a las bibliotecas de control extra que están cargadas.
También he encontrado que aunque el GC puede tratar con referencias circulares, parece tener problemas (a veces) con referencias circulares de controladores de eventos.IE object1 hace referencia a object2 y object1 tiene un método que maneja y evento desde object2. Encontré circunstancias en las que esto no liberaba los objetos cuando esperaba, pero nunca pude volver a producirlos en un caso de prueba.

16

Como ya se ha dicho, GC no tiene problemas con las referencias circulares. Me gustaría agregar, que un lugar común para filtrar memoria en .NET son los manejadores de eventos. Si uno de sus formularios tiene un controlador de eventos adjunto a otro objeto que está "vivo", entonces hay una referencia a su formulario y el formulario no recibirá GC'd.

+2

Una respuesta que describe su ejemplo: http://stackoverflow.com/a/12133788/6345 –

12

La recolección de basura funciona mediante el seguimiento de las raíces de la aplicación. Las raíces de aplicaciones son ubicaciones de almacenamiento que contienen referencias a objetos en el montón administrado (o nulo). En .NET, las raíces son

  1. Las referencias a objetos globales
  2. Las referencias a objetos estáticos
  3. Las referencias a campos estáticos
  4. referencias sobre la pila de objetos locales
  5. referencias en la pila de oponerse parámetros pasó a los métodos
  6. Referencias a objetos que esperan ser finalizados
  7. Referencias en registros de CPU a objetos en el ap

La lista de las raíces activas es mantenida por el CLR. El recolector de basura funciona mirando los objetos en el montón administrado y viendo a los que todavía puede acceder la aplicación, es decir, accesible a través de una raíz de aplicación. Tal objeto se considera rooteado.

Supongamos ahora que tiene un formulario principal que contiene referencias a formularios secundarios y estos formularios secundarios contienen referencias al formulario primario. Además, suponga que la aplicación ya no contiene referencias al padre de ninguno de los formularios secundarios ni a ninguno de ellos. Luego, para los propósitos del recolector de basura, estos objetos gestionados ya no se rootean y se recogerán como basura la próxima vez que se produzca una recolección de basura.

+0

@Jason, ¿qué quiere decir con un "parámetro de objeto"? Y creo que la ubicación de la referencia es el determinante crítico ... Si en la pila, o un miembro estático de una clase, o en un registro de CPU, entonces es una referencia de raíz. ... de lo contrario no. (a excepción de la cola inalcanzable, - otro tema) –

2

Me gustaría hacerme eco del comentario de Vilx sobre eventos y recomendar un patrón de diseño que ayude a solucionarlo.

Digamos que usted tiene un tipo que es un origen de eventos, por ejemplo .:

interface IEventSource 
{ 
    event EventHandler SomethingHappened; 
} 

Aquí hay un fragmento de una clase que gestiona los eventos de las instancias de ese tipo. La idea es que cada vez que asigne una nueva instancia a la propiedad, primero se da de baja de cualquier tarea previa y luego se suscribe a la nueva instancia. Las comprobaciones nulas aseguran los comportamientos límite correctos, y más al punto, simplifican la eliminación: todo lo que haces es anular la propiedad.

Lo que plantea el punto de eliminación. Cualquier clase que se suscriba a eventos debe implementar la interfaz IDisposable porque los eventos son recursos administrados. (N.B. omití una implementación adecuada del patrón Dispose en el ejemplo por brevedad, pero se entiende la idea).

class MyClass : IDisposable 
{ 
    IEventSource m_EventSource; 
    public IEventSource EventSource 
    { 
     get { return m_EventSource; } 
     set 
     { 
      if(null != m_EventSource) 
      { 
       m_EventSource -= HandleSomethingHappened; 
      } 
      m_EventSource = value; 
      if(null != m_EventSource) 
      { 
       m_EventSource += HandleSomethingHappened; 
      } 
     } 
    } 

    public Dispose() 
    { 
     EventSource = null; 
    } 

    // ... 
} 
Cuestiones relacionadas