2011-05-20 57 views
28

Tengo un temporizador en C# que ejecuta algún código dentro de su método. Dentro del código estoy usando varios objetos temporales.¿Cuál es la forma correcta de liberar memoria en C#

  1. Si tengo algo así como Foo o = new Foo(); dentro del método, ¿quiere decir que cada vez que el temporizador de garrapatas, estoy creando un nuevo objeto y una nueva referencia a ese objeto?

  2. Si tengo string foo = null y luego acabo de poner algo temporal en foo, ¿es lo mismo que el anterior?

  3. ¿El recolector de basura alguna vez elimina el objeto y la referencia u objetos se crean continuamente y permanecen en la memoria?

  4. Si acabo de declarar Foo o; y no lo apunto a ninguna instancia, ¿no se elimina cuando el método finaliza?

  5. Si quiero asegurar que todo se borra, ¿cuál es la mejor forma de hacerlo:

    • con la instrucción using dentro del método
    • llamando al método dispose al final
    • poniendo Foo o; fuera del método del temporizador y simplemente hace la asignación o = new Foo() dentro, entonces el puntero al objeto se elimina después de que el método finalice, el recolector de basura eliminará el objeto. recolector de basura

Respuesta

29

1.Si tengo algo así como Foo o = new Foo(); dentro del método, ¿significa que significa que cada vez que el temporizador marca, estoy creando un nuevo objeto y una nueva referencia a ese objeto?

Sí.

2. Si he cadena foo = null y luego sólo hay que poner algo temporal en foo, es el mismo que el anterior?

Si está preguntando si el comportamiento es el mismo, entonces sí.

3.Does el recolector de basura cada vez eliminar el objeto y la referencia o objetos se crean continuamente y estancia en la memoria?

La memoria utilizada por esos objetos se recoge con toda seguridad después de que las referencias se consideren no utilizadas.

4.Si declaro Foo o; y no apuntarlo a ninguna instancia, ¿no es eso eliminado cuando finaliza el método?

No, ya que no se ha creado ningún objeto, entonces no hay objeto para recopilar (no es la palabra correcta).

5.Si quiero asegurar que todo se borra, lo que es la mejor manera de hacerlo

Si la clase del objeto implementa IDisposable entonces ciertamente desea llamar con avidez Dispose tan pronto como sea posible. La palabra clave using lo hace más fácil porque llama al Dispose automáticamente de forma segura.

Aparte de eso, no hay nada más que deba hacer, excepto dejar de usar el objeto. Si la referencia es una variable local, cuando se sale del alcance será elegible para la recopilación. Si se trata de una variable de nivel de clase, es posible que deba asignarle null para que sea elegible antes de que la clase que lo contiene sea elegible.


Esto es técnicamente incorrecto (o al menos un poco engañoso). Un objeto puede ser elegible para la recopilación mucho antes de que salga del alcance. El CLR está optimizado para recolectar memoria cuando detecta que una referencia ya no se usa. En casos extremos, el CLR puede recolectar un objeto incluso cuando uno de sus métodos aún se está ejecutando.

Actualización:

Aquí es un ejemplo que demuestra que la GC se recogen objetos a pesar de que todavía pueden estar en el estudio. Debe compilar una compilación de versión y ejecutar esto fuera del depurador.

static void Main(string[] args) 
{ 
    Console.WriteLine("Before allocation"); 
    var bo = new BigObject(); 
    Console.WriteLine("After allocation"); 
    bo.SomeMethod(); 
    Console.ReadLine(); 
    // The object is technically in-scope here which means it must still be rooted. 
} 

private class BigObject 
{ 
    private byte[] LotsOfMemory = new byte[Int32.MaxValue/4]; 

    public BigObject() 
    { 
     Console.WriteLine("BigObject()"); 
    } 

    ~BigObject() 
    { 
     Console.WriteLine("~BigObject()"); 
    } 

    public void SomeMethod() 
    { 
     Console.WriteLine("Begin SomeMethod"); 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     Console.WriteLine("End SomeMethod"); 
    } 
} 

En mi máquina del finalizador se ejecuta mientras se sigue ejecutando SomeMethod!

+1

¿cómo exactamente terminarías dentro del método de un objeto sin que ese objeto tenga una referencia rooteada? – Yaur

+0

@Yaur: Buena pregunta: Considere un método de instancia que no use otros miembros de instancia (variables o métodos). Eso significa que la referencia del objeto solo necesita extraerse para pasar la referencia 'this'. Después de eso, el objeto es técnicamente elegible siempre que el CLR pueda detectar que no se usa más adelante aunque todavía esté rooteado. –

+0

@Brian Gideon Veo su punto, pero soy escéptico ya que el CLR utiliza la marca y el barrido para determinar si el GC es elegible para que esto suceda o que el objeto es incluso técnicamente "elegible" para la recolección de basura.Pero esto es, en cualquier caso, un caso bastante interesante para hacer algunas pruebas. – Yaur

15

El .NET se encarga de todo esto para usted.

Puede determinar cuándo los objetos ya no se referencian y (eventualmente) liberar la memoria que se les ha asignado.

+0

¿Estás seguro de que incluso los objetos que se crean dentro del temporizador se eliminan? Porque encontré un problema en el que mi aplicación en 10 minutos tenía 700mb y supongo que algo va mal con los objetos que deberían eliminarse en el temporizador. – user579674

+0

A menos que esté haciendo algo para mantener esas referencias creadas dentro del alcance del método "marcar" del temporizador, se liberarán * en algún momento * después de que el método finalice. Esto depende de cuándo se ejecuta el recolector de elementos no utilizados y detecta que todavía no hay nada que señale esos objetos. Si su temporizador sigue un ciclo lo suficientemente rápido, puedo ver cómo sería posible consumir una gran cantidad de memoria. – Yuck

+3

Se ocupa de todo esto ... excepto cuando no lo hace. No preocuparse por la gestión de la memoria es una excelente manera de salir del crecimiento del montón de control. –

5

Los objetos son eligables para la recolección de basura una vez que quedan fuera del alcance se vuelven inalcanzables (gracias ben!). La memoria no se liberará a menos que el recolector de basura crea que se está quedando sin memoria.

Para los recursos administrados, el recolector de basura sabrá cuándo es esto, y usted no necesita hacer nada.

Para recursos no administrados (como conexiones a bases de datos o archivos abiertos) el recolector de basura no tiene forma de saber cuánta memoria están consumiendo, y es por eso que debe liberarlos manualmente (usando dispose, o mucho mejor aún el bloque que usa)

Si no se liberan objetos, ya sea que tenga mucha memoria y no haya necesidad, o los mantiene en su aplicación, y por lo tanto el recolector de basura no los liberará (en caso de que realmente utilice esta referencia que mantuvo)

+2

s/fuera de alcance/inalcanzable /. El alcance solo es relevante para las variables locales (no capturadas) de tipo de valor. –

+0

Abouth los archivos abiertos que mencionó, es el tamaño del archivo en el disco la misma cantidad de memoria que el archivo tiene en la memoria (suponiendo que no se haya alterado). – user579674

+0

No, a menos que lea todo el archivo en la memoria. Cuando abre un archivo, el sistema operativo puede bloquearlo de diferentes maneras (lectura/escritura, etc.). La recolección de elementos no utilizados no es determinista (es decir, sucede cuando es necesario), por lo que si el archivo no se libera explícitamente, el archivo podría permanecer bloqueado durante más tiempo. Los manejadores de archivos también son limitados, y el recolector de basura no sabe nada de esto, por lo que los archivos deben cerrarse, no dejarse al recolector de basura –

2
  1. ¿Qué quiere decir con lo mismo? Se volverá a ejecutar cada vez que se ejecute el método.
  2. Sí, el recolector de basura .Net usa un algoritmo que comienza con cualquier variable global/en el ámbito, las recorre mientras sigue cualquier referencia que encuentre recursivamente, y elimina cualquier objeto en la memoria que se considere inalcanzable.see here for more detail on Garbage Collection
  3. Sí, la memoria de todas las variables declaradas en un método se libera cuando el método sale, ya que todas son inalcanzables. Además, cualquier variable que sea declarada pero nunca utilizada será optimizada por el compilador, por lo que en realidad su variable Foo nunca ocupará memoria.
  4. la instrucción using simplemente invoca disponer de un objeto IDisposable cuando sale, por lo que esto es equivalente a su segundo punto de viñeta. Ambos indicarán que ha terminado con el objeto y le informarán al GC que está listo para dejarlo. Sobrescribir la única referencia al objeto tendrá un efecto similar.
+1

El enlace proporcionado por CrazyJugglerDrummer es una buena lectura en GC. –

1

El recolector de basura vendrá y limpiará todo lo que ya no tenga referencias. A menos que tenga recursos no administrados dentro de Foo, llamar a Dispose o usar una instrucción using en él no le ayudará mucho.

Estoy bastante seguro de que esto se aplica, ya que todavía estaba en C#. Pero, tomé un curso de diseño de juegos usando XNA y pasamos un tiempo hablando sobre el recolector de basura para C#. La recolección de basura es costosa, ya que debe verificar si tiene alguna referencia al objeto que desea recolectar. Entonces, el GC intenta postergar esto el mayor tiempo posible. Por lo tanto, siempre y cuando no se esté quedando sin memoria física cuando su programa llegó a los 700 MB, es posible que el GC sea perezoso y no se preocupe por eso todavía.

Pero, si solo usa Foo o fuera del ciclo y crea un o = new Foo() cada vez, todo debería funcionar bien.

2

Respondamos sus preguntas una por una.

  1. Sí, crea un objeto nuevo cada vez que se ejecuta esta instrucción, sin embargo, sale "fuera del alcance" cuando sale del método y es elegible para la recolección de basura.
  2. Bueno, esto sería igual a # 1, excepto que ha usado un tipo de cadena. Un tipo de cadena es inmutable y obtienes un nuevo objeto cada vez que realizas una tarea.
  3. Sí, el recolector de elementos no utilizados recoge los objetos fuera del ámbito, a menos que asigne el objeto a una variable con un ámbito grande, como la variable de clase.
  4. Sí.
  5. La sentencia using solo se aplica a los objetos que implementan la interfaz IDisposable. Si ese es el caso, usarlo es lo mejor para los objetos dentro del alcance de un método. No pongas Foo o en un alcance mayor a menos que tengas una buena razón para hacerlo. Lo mejor es limitar el alcance de cualquier variable al alcance más pequeño que tenga sentido.
2

Aquí está una descripción rápida:

  • Una vez que las referencias se han ido, su objeto será probablemente ser basura recogida.
  • Solo puede contar con una recopilación estadística que mantenga el tamaño de su pila normal siempre que todas las referencias a la basura hayan desaparecido. En otras palabras, no hay garantía de que un objeto específico sea recogido basura.
    • De esto se deduce que nunca se garantizará que se llame su finalizador. Evite los finalizadores.
  • Dos fuentes comunes de fugas:
    • controladores de eventos y delegados son referencias. Si te suscribes a un evento de un objeto, te estás refiriendo a él. Si tiene un delegado en el método de un objeto, lo está haciendo referencia.
    • Los recursos no administrados, por definición, no se recopilan automáticamente. Esto es para lo que es el patrón IDisposable.
  • Por último, si desea una referencia que no impida que el objeto se recopile, consulte WeakReference.

Una última cosa: si declara Foo foo; sin asignarlo, no tiene que preocuparse, no se filtra nada. Si Foo es un tipo de referencia, no se creó nada. Si Foo es un tipo de valor, se asigna en la pila y, por lo tanto, se limpiará automáticamente.

1

Como señala Brian, el GC puede recoger todo lo que no se puede alcanzar, incluidos los objetos que todavía están en el alcance e incluso mientras los métodos de instancia de esos objetos todavía se están ejecutando. considere el siguiente código:

class foo 
{ 
    static int liveFooInstances; 

    public foo() 
    { 
     Interlocked.Increment(ref foo.liveFooInstances); 
    } 

    public void TestMethod() 
    { 
     Console.WriteLine("entering method"); 
     while (Interlocked.CompareExchange(ref foo.liveFooInstances, 1, 1) == 1) 
     { 
      Console.WriteLine("running GC.Collect"); 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
     } 
     Console.WriteLine("exiting method"); 
    } 

    ~foo() 
    { 
     Console.WriteLine("in ~foo"); 
     Interlocked.Decrement(ref foo.liveFooInstances); 
    } 

} 

class Program 
{ 

    static void Main(string[] args) 
    { 
     foo aFoo = new foo(); 
     aFoo.TestMethod(); 
     //Console.WriteLine(aFoo.ToString()); // if this line is uncommented TestMethod will never return 
    } 
} 

si se ejecuta con una versión de depuración, con el depurador asociado, o con la línea especificada sin comentar TestMethod nunca volverá. Pero corriendo sin un depurador conectado, TestMethod volverá.

Cuestiones relacionadas