2009-05-05 20 views
36

he crear una aplicación de C# que utiliza hasta 150 MB de memoria (bytes privados), debido principalmente a un gran diccionario:liberar memoria explícitamente en C#

Dictionary<string, int> Txns = new Dictionary<string, int>(); 

me preguntaba cómo liberar esta memoria hasta. He intentado esto:

Txns = null; 
GC.Collect(); 

Pero no parece hacer mucha mella en mis bytes privados - que haya que dejar de decir 155 Mb a 145MB. ¿Alguna pista?

Gracias

operación -Editar-

Está bien que estoy teniendo más suerte con este código (se pone los bytes privados hasta 50 MB), pero ¿por qué?

Txns.Clear(); // <- makes all the difference 
Txns = null; 
GC.Collect(); 

operación -Editar-

Está bien para todo el que dice 'no utilizan gc.collect', justo lo suficiente (que no voy a discutir que, aparte de decir que puede ver mi fondo C llegando), pero realmente no responde mi pregunta: ¿Por qué el recolector de basura solo libera la memoria si borro primero la lista de transacciones? ¿No debería liberar la memoria de todos modos, ya que el diccionario ha sido desreferenciado?

+0

GC.Collect() no exige que toda la memoria que se pueda recopilar se recopile en ese momento, ya que la recolección de basura es un proceso de varios pasos. Un proceso que podría agregar es que generalmente no necesita saber los detalles sobre ... – peSHIr

+0

¿Es necesario establecer Txns como nulo? ¿Hará alguna diferencia? o simplemente Borrar el contenido es suficiente (para liberar memoria)? – akjoshi

Respuesta

16

Los bytes privados reflejan el uso de memoria del proceso. Cuando se recopilan objetos, el segmento de memoria asociado puede o no liberarse en el sistema operativo. El CLR administra la memoria en el nivel del sistema operativo y, como la asignación y liberación de la memoria no es gratuita, no hay razón para liberar cada parte de la memoria de inmediato ya que es probable que la aplicación solicite más memoria más adelante.

+2

Todo eso suena razonable, pero realmente _desea_ liberar la memoria del sistema operativo si llamo a Txns.Clear(). Además, realmente quiero ser amable con la memoria del sistema operativo, ya que no soy el único que usa este servidor. – Chris

+2

Supongo que lo que estoy preguntando es, ¿por qué txns.Clear() devuelve la memoria al sistema operativo, cuando la referenciación del diccionario completo no la devuelve? – Chris

+3

El punto es que en una aplicación administrada está a un paso de tratar directamente con la memoria del sistema operativo ya que CLR lo hace por usted. Puede construir escenarios en los que pueda observar un efecto directo tal como lo describe, pero no está garantizado que funcione de acuerdo con los detalles de implementación del CLR. –

4

No estoy seguro de la memoria si Dictionary tiene un Dispose() o no, pero está obligado a tener un Clear(). Llame a cualquiera de estos antes de establecer cualquier referencia al null.

Luego, simplemente deje que el recolector de basura haga su trabajo. Es casi nunca una buena idea llamar al GC.Collect() explícitamente usted mismo y es posible que ni siquiera haga lo que quiera/necesite/espere y termine costándole rendimiento. El análisis de código estático (= FxCop) no le avisa con Reliability rule CA2001 acerca de esto por nada, ¿sabe? Simplemente no hagas esto a menos que realmente sepas lo que estás haciendo. Y aun así no lo hagas. ;-)

¿Estás seguro de que el diccionario es tan grande? ¿No son solo 10 Mb de memoria y el resto es tomado por tu aplicación? Pregunta que podría ayudarlo: ¿ha utilizado un generador de perfiles para ver dónde se consume realmente la memoria ...?

+0

El diccionario * es * tan grande. Cuando ejecuté el código con el cambio .Clear, el uso de memoria va de 160 mb a 50 mb. Entonces supongo que el diccionario es de 110mb. – Chris

-1

Generalmente no es una buena idea intentar forzar el GC. ¿De verdad necesitas tener todo el diccionario en la memoria?

+0

Buena pregunta. Podría ser un problema de diseño. – peSHIr

+0

Sí, lo necesito, es para una gran reconciliación (compare las filas de una tabla a la otra). 150mb es cacahuetes. – Chris

+2

150 mb es apenas cacahuetes. Si realmente lo necesita, está bien y muy bien, pero la mayoría de las veces, las declaraciones de ese tipo implican algún tipo de problema de diseño o la ignorancia con respecto a una mejor manera de hacerlo. Quiero decir que no es un insulto, solo una observación. – Chris

11

si llama a GC.Collect() comienza a hacer su trabajo, pero vuelve inmediatamente, no bloquea, por lo que no ve su efecto, si solo llama a GC.WaitForPendingFinalizers() después de eso, bloqueará su aplicación hasta que GC.Collect() finalice su trabajo

+1

¡Yikes! Ni siquiera se puede empezar a pensar en los niveles de maldad de iniciar un hilo de forma periódica (y bastante a menudo: ¡se intenta dos veces por segundo!) Llama a GC.Collect y luego espera a que finalicen todos los pendientes. ¿No estás totalmente interesado en las aplicaciones de escalado? ¡Eso solo está tratando de ocultar un mal síntoma, mal! Supongo que tendré que -1 aquí ... – peSHIr

+8

@peSHIr: estoy de acuerdo, aunque para ser justos, sí contesta explícitamente la pregunta. Si es una buena idea o no es una pregunta diferente. – andy

+0

@andy: a regañadientes de acuerdo. ¿Debo I -1 la pregunta en su lugar, entonces ...? También parece innecesariamente áspero e inútil. Oh bien. – peSHIr

5

Es muy probable que tenga una referencia oculta al diccionario en otro lugar. Por lo tanto, el diccionario no se recopila, pero si lo Clear(), los contenidos se recopilan.

Como ya se ha mencionado, no se recomienda forzar el GC. Esto podría llevar a que la memoria se introduzca en "generaciones" superiores que a menudo no se recopilan, desperdiciando así más memoria que la obtenida a largo plazo.

+0

No, no hay referencias ocultas al diccionario. – Chris

+0

¿Su comentario sobre empujar objetos hacia generaciones superiores, se preocupa por elaborar? Eso suena como un problema para tener en cuenta. – Chris

+0

Espera, pensé que si llamas a gc.collect sin argumentos, simplemente recoge _ todas_ generaciones? ¿No descartaría esto el problema de empujar cosas hacia las generaciones superiores? Gracias. – Chris

1

Editar:

Para ser justos, el establecimiento de la referencia a la nula no libera la memoria, asigna su contenedor a una dirección diferente, en este caso nulo. De acuerdo con MSDN, llamando al Clear(), hace esto: "La propiedad Count se establece en 0 y las referencias a otros objetos de los elementos de la colección también se liberan. La capacidad permanece sin cambios".

...

Nunca se debe llamar al recolector de basura. Está utilizando objetos administrados sin recursos nativos, confíe en el recolector de elementos no utilizados para limpiarlos después de usted.

Además del tamaño de su diccionario, no necesita preocuparse por la memoria, la memoria no es su problema, es un problema para los recolectores de basura.

Llamar a Clear() eliminará las referencias a cualquier objeto contenido en el interior, pero la capacidad no se modifica.

En una nota técnica, la recolección de memoria es costosa y consume mucho tiempo de operación. Por lo que respecta a la razón, el GC no solo maneja la memoria del montón y limpia su montón, sino que también desfragma el montón. Intenta mover la memoria a bloques contiguos para agilizar la asignación cuando alguna parte del código realiza una solicitud grande.

p.s. ¿Qué tan grande es su diccionario que usa 155MB de memoria?

+0

Es grande, estoy reconciliando un millón de transacciones. – Chris

+0

basura santa que es una gran cantidad de transacciones. Asumo que esto es una cosa de una sola vez, o ¿necesita hacer esto regularmente? – Chris

+0

Una vez al día, todos los días. – Chris

0

Windows tiene dos eventos de disponibilidad de memoria. Esperaría que el CLR responda a esto. Si hay suficiente memoria disponible, lo más inteligente es NO ejecutar el recolector de basura. Por lo tanto, para asegurarse de que realmente está observando un mal comportamiento CLR, repita esta prueba con otra aplicación ficticia utilizando un gran montón de memoria.

1

¿Necesita recuperar la memoria? La memoria está disponible, simplemente no está siendo reclamada. No debe borrar el diccionario, mantenga un weak reference y deje que el tiempo de ejecución haga su trabajo.

Si desea ver realmente lo que está pasando, consulte .NET Memory Profiler. Eso le dará visibilidad de lo que está ocurriendo exactamente con su objeto, de qué generación es, qué memoria está usando cada cosa y así sucesivamente. :)

+0

No * necesito * recuperar la memoria, sin embargo, en nuestra situación, preferiría devolverlo al sistema operativo rápidamente, si es posible. Supongo que esta pregunta se está volviendo más teórica de "cómo hacer esto". en lugar de 'realmente necesito hacer esto' ... – Chris

1

Ok, tengo una teoría aquí ... Dictionary es una colección de KeyValuePair que es nuevamente un tipo de referencia.

Su diccionario contiene estos keyValuePairs. Cuando uno dice:

Txns = null 

Se libera la referencia 'txns' de aquellos colección KeyValuePair. Pero aún la memoria real de 150 Mb está siendo referenciada por esos KeyValuePair y están dentro del alcance, por lo tanto, no están listos para la recolección de basura.

Pero cuando se utiliza la siguiente:

Txns.Clear(); 
Txns = null; 
GC.Collect(); 

Aquí, el método claro también libera el 150 Mb de datos de sus respectivas referencias a objetos KeyValuePair. Por lo tanto, esos objetos estaban listos para la recolección de basura.

Es solo una suposición salvaje que estoy haciendo aquí. Los comentarios son bienvenidos :)

+0

Los KeyValuePairs no están dentro del alcance. Para tenerlos en el alcance, tendría que configurar variables adicionales o una matriz para mantener copias de KeyValuePairs, similar a esto: 'var arrayOfReferencesToKeyValuePairs = Txns.ToArray();' En su escenario, una vez que 'Txns' es nulo, no hay forma de acceder a ninguno de los KeyValuePairs y el recolector de basura lo ve. – jnm2

Cuestiones relacionadas