funciona example como collector clean c# .net memory-management garbage-collection

c# - example - ¿Cómo puedo obtener.NET para recolectar basura de manera agresiva?



garbage collector c# como funciona (12)

¿Has probado las fugas de memoria? He estado usando .NET Memory Profiler con bastante éxito en un proyecto que tenía una cantidad de fugas de memoria muy sutiles y molestamente persistentes (con intención).

Justo como un control de cordura, asegúrese de estar llamando Dispose en cualquier objeto que implemente IDisposable .

Tengo una aplicación que se utiliza en el procesamiento de imágenes, y normalmente me encuentro asignando matrices en el tamaño de 4.000x4000 ushort, así como el float ocasional y similares. Actualmente, el framework .NET tiende a bloquearse en esta aplicación aparentemente al azar, casi siempre con un error de falta de memoria. 32mb no es una gran declaración, pero si .NET está fragmentando la memoria, entonces es muy posible que tales grandes asignaciones continuas no se comporten como se esperaba.

¿Hay alguna manera de decirle al recolector de basura que sea más agresivo o defragmente la memoria (si ese es el problema)? Me doy cuenta de que están las llamadas GC.Collect y GC.WaitForPendingFinalizers, y las he rociado bastante liberalmente a través de mi código, pero sigo recibiendo los errores. Puede ser porque llamo mucho a las rutinas dll que usan código nativo, pero no estoy seguro. He repasado ese código de C ++ y me aseguro de que elimine cualquier memoria que declaro eliminar, pero sigo teniendo estos bloqueos de C #, así que estoy bastante seguro de que no está allí. Me pregunto si las llamadas C ++ podrían estar interfiriendo con el GC, haciendo que deje atrás la memoria porque una vez interactuó con una llamada nativa, ¿es posible? Si es así, ¿puedo desactivar esa funcionalidad?

EDITAR: Aquí hay un código muy específico que causará el bloqueo. De acuerdo con esta pregunta SO , no necesito deshacerme de los objetos de BitmapSource aquí. Aquí está la versión ingenua, sin GC. Se recoge en ella. Por lo general, se bloquea en la iteración 4 a 10 del procedimiento de deshacer. Este código reemplaza al constructor en un proyecto WPF en blanco, ya que estoy usando WPF. Hago la locura con el bitmapsource debido a las limitaciones que expliqué en mi respuesta a @dthorpe a continuación, así como a los requisitos enumerados en esta pregunta SO .

public partial class Window1 : Window { public Window1() { InitializeComponent(); //Attempts to create an OOM crash //to do so, mimic minute croppings of an ''image'' (ushort array), and then undoing the crops int theRows = 4000, currRows; int theColumns = 4000, currCols; int theMaxChange = 30; int i; List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack byte[] displayBuffer = null;//the buffer used as a bitmap source BitmapSource theSource = null; for (i = 0; i < theMaxChange; i++) { currRows = theRows - i; currCols = theColumns - i; theList.Add(new ushort[(theRows - i) * (theColumns - i)]); displayBuffer = new byte[theList[i].Length]; theSource = BitmapSource.Create(currCols, currRows, 96, 96, PixelFormats.Gray8, null, displayBuffer, (currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8); System.Console.WriteLine("Got to change " + i.ToString()); System.Threading.Thread.Sleep(100); } //should get here. If not, then theMaxChange is too large. //Now, go back up the undo stack. for (i = theMaxChange - 1; i >= 0; i--) { displayBuffer = new byte[theList[i].Length]; theSource = BitmapSource.Create((theColumns - i), (theRows - i), 96, 96, PixelFormats.Gray8, null, displayBuffer, ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8); System.Console.WriteLine("Got to undo change " + i.ToString()); System.Threading.Thread.Sleep(100); } } }

Ahora, si soy explícito al llamar al recolector de elementos no utilizados, tengo que ajustar todo el código en un bucle externo para provocar el bloqueo de OOM. Para mí, esto tiende a suceder alrededor de x = 50 o así:

public partial class Window1 : Window { public Window1() { InitializeComponent(); //Attempts to create an OOM crash //to do so, mimic minute croppings of an ''image'' (ushort array), and then undoing the crops for (int x = 0; x < 1000; x++){ int theRows = 4000, currRows; int theColumns = 4000, currCols; int theMaxChange = 30; int i; List<ushort[]> theList = new List<ushort[]>();//the list of images in the undo/redo stack byte[] displayBuffer = null;//the buffer used as a bitmap source BitmapSource theSource = null; for (i = 0; i < theMaxChange; i++) { currRows = theRows - i; currCols = theColumns - i; theList.Add(new ushort[(theRows - i) * (theColumns - i)]); displayBuffer = new byte[theList[i].Length]; theSource = BitmapSource.Create(currCols, currRows, 96, 96, PixelFormats.Gray8, null, displayBuffer, (currCols * PixelFormats.Gray8.BitsPerPixel + 7) / 8); } //should get here. If not, then theMaxChange is too large. //Now, go back up the undo stack. for (i = theMaxChange - 1; i >= 0; i--) { displayBuffer = new byte[theList[i].Length]; theSource = BitmapSource.Create((theColumns - i), (theRows - i), 96, 96, PixelFormats.Gray8, null, displayBuffer, ((theColumns - i) * PixelFormats.Gray8.BitsPerPixel + 7) / 8); GC.WaitForPendingFinalizers();//force gc to collect, because we''re in scenario 2, lots of large random changes GC.Collect(); } System.Console.WriteLine("Got to changelist " + x.ToString()); System.Threading.Thread.Sleep(100); } } }

Si mal manejo de memoria en cualquier escenario, si hay algo que debería detectar con un generador de perfiles, házmelo saber. Esa es una rutina bastante simple allí.

Desafortunadamente, parece que la respuesta de @ Kevin es correcta, esto es un error en .NET y cómo .NET maneja objetos de más de 85k. Esta situación me parece extremadamente extraña; podría reescribirse Powerpoint en .NET con este tipo de limitación, o con cualquiera de las otras aplicaciones de suite de Office? 85k no me parece que sea mucho espacio, y también creo que cualquier programa que use las asignaciones llamadas ''grandes'' con frecuencia se volvería inestable en cuestión de días o semanas al usar .NET.

EDITAR : Parece que Kevin tiene razón, esta es una limitación del GC de .NET. Para aquellos que no quieren seguir el hilo completo, .NET tiene cuatro montones GC: gen0, gen1, gen2 y LOH (Large Object Heap). Todo lo que tiene 85k o menos va en uno de los primeros tres montones, dependiendo del tiempo de creación (movido de gen0 a gen1 a gen2, etc.). Los objetos de más de 85k se colocan en el LOH. El LOH nunca se compacta, por lo que eventualmente, las asignaciones del tipo que estoy haciendo eventualmente causarán un error OOM a medida que los objetos se dispersen en ese espacio de memoria. Hemos descubierto que mudarse a .NET 4.0 ayuda un poco al problema, retrasando la excepción, pero no impidiéndola. Para ser honesto, esto se parece un poco a la barrera de los 640k: 85k debería ser suficiente para cualquier aplicación de usuario (para parafrasear este video de una discusión del GC en .NET). Para el registro, Java no muestra este comportamiento con su GC.


Además de manejar las asignaciones de una manera más amigable para GC (por ejemplo, reutilización de arreglos, etc.), ahora hay una nueva opción: puede causar la compactación manual de LOH.

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;

Esto causará una compactación LOH la próxima vez que suceda una colección gen-2 (ya sea solo o mediante su llamada explícita a GC.Collect ).

Tenga en cuenta que no es una buena idea compactar el LOH. Es solo que su escenario es lo suficientemente decente como para permitir la compactación manual. El LOH se usa generalmente para objetos grandes y de larga vida, como búferes preasignados que se reutilizan con el tiempo, etc.

Si su versión de .NET aún no es compatible con esto, también puede intentar asignar tamaños de potencias de dos, en lugar de asignar con precisión la cantidad de memoria que necesita. Esto es lo que hacen muchos asignadores nativos para garantizar que la fragmentación de la memoria no se vuelva increíblemente estúpida (básicamente pone un límite superior en la fragmentación máxima del montón). Es molesto, pero si puedes limitar el código que maneja esto a una pequeña porción de tu código, es una solución decente.

Tenga en cuenta que todavía tiene que asegurarse de que es posible compactar el montón: cualquier memoria inmovilizada evitará la compactación en el montón en el que vive.

Otra opción útil es usar paginación, nunca asignando más de, digamos, 64 kB de espacio contiguo en el montón; esto significa que evitará usar el LOH por completo. No es demasiado difícil administrar esto en un simple "envoltorio de matriz" en su caso. La clave es mantener un buen equilibrio entre los requisitos de rendimiento y la abstracción razonable.

Y, por supuesto, como último recurso, siempre puede usar un código inseguro. Esto le da mucha flexibilidad en el manejo de las asignaciones de memoria (aunque es un poco más doloroso que usar, por ejemplo, C ++), lo que le permite asignar explícitamente la memoria no administrada, hacer su trabajo con eso y liberar la memoria manualmente. Nuevamente, esto solo tiene sentido si puede aislar este código en una pequeña porción de su base de código total, y asegúrese de tener un contenedor administrado seguro para la memoria, incluido el finalizador apropiado (para mantener un nivel decente de seguridad de la memoria) . No es demasiado difícil en C #, aunque si te encuentras haciendo esto con demasiada frecuencia, podría ser una buena idea usar C ++ / CLI para esas partes del código y llamarlas desde tu código C #.


Aquí hay algunos artículos que detallan problemas con el Heap de objetos grandes. Suena como lo que te puedes encontrar.

http://connect.microsoft.com/VisualStudio/feedback/details/521147/large-object-heap-fragmentation-causes-outofmemoryexception

Peligros del gran montón de objetos:
http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/

Aquí hay un enlace sobre cómo recopilar datos en el gran montón de objetos (LOH):
http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

De acuerdo con esto, parece que no hay forma de compactar el LOH. No puedo encontrar nada más nuevo que explícitamente dice cómo hacerlo, y parece que no ha cambiado en el tiempo de ejecución 2.0:
http://blogs.msdn.com/maoni/archive/2006/04/18/large-object-heap.aspx

La forma simple de manejar el problema es hacer pequeños objetos si es posible. Su otra opción es crear solo unos pocos objetos grandes y reutilizarlos una y otra vez. No es una situación de idea, pero podría ser mejor que volver a escribir la estructura del objeto. Como dijo que los objetos creados (matrices) son de diferentes tamaños, podría ser difícil, pero podría evitar que la aplicación se bloquee.


Comience por reducir el problema donde se encuentra el problema. Si tienes una pérdida de memoria nativa, pinchar el GC no va a hacer nada por ti.

Ejecute perfmon y mire el tamaño del montón de .NET y los contadores de Bytes privados. Si el tamaño del montón sigue siendo bastante constante, pero los bytes privados están creciendo, entonces tienes un problema con el código nativo y tendrás que dividir las herramientas de C ++ para depurarlo.

Suponiendo que el problema es con el montón de .NET debe ejecutar un generador de perfiles contra el código como el perfilador Ant de Redgate o el DotTrace de JetBrain. Esto le dirá qué objetos ocupan el espacio y no se recogen rápidamente. También puede usar WinDbg con SOS para esto, pero es una interfaz complicada (aunque muy potente).

Una vez que haya encontrado los artículos ofensivos, debería ser más obvio cómo lidiar con ellos. Algunas de las cosas que causan problemas son campos estáticos que hacen referencia a objetos, controladores de eventos que no están desregistrados, objetos que viven lo suficiente como para entrar en Gen2 pero que mueren poco después, etc. Sin un perfil del montón de memoria no estarás capaz de identificar la respuesta.

Sin embargo, haga lo que haga, "rociar liberalmente" GC. Recoger llamadas casi siempre es la manera incorrecta de tratar de resolver el problema.

Existe una posibilidad remota de que cambiar a la versión de servidor del GC mejore las cosas (solo una propiedad en el archivo de configuración): la versión de estación de trabajo predeterminada está orientada a mantener una UI receptiva, por lo que abandonará las grandes colecciones de larga ejecución.


El GC no tiene en cuenta el montón no gestionado. Si está creando muchos objetos que son meramente envoltorios en C# para una memoria no administrada más grande, entonces su memoria está siendo devorada, pero el GC no puede tomar decisiones racionales basándose en esto, ya que solo ve el montón administrado.

Usted termina en una situación en la que el GC no cree que le falte memoria porque la mayoría de las cosas en su gen 1 son referencias de 8 bytes donde en realidad son como icebergs en el mar. ¡La mayor parte de la memoria está abajo!

Puede hacer uso de estas llamadas GC :

  • System :: GC :: AddMemoryPressure (sizeOfField);
  • Sistema :: GC :: RemoveMemoryPressure (sizeOfField);

Estos métodos permiten que el GC vea la memoria no administrada (si le proporciona las cifras correctas).


El problema probablemente se deba a la cantidad de estos objetos grandes que tiene en la memoria. La fragmentación sería un problema más probable si se tratara de tamaños variables (aunque todavía podría ser un problema). En los comentarios se indicó que está almacenando una pila de deshacer en la memoria para los archivos de imagen. Si mueve esto a Disco, se ahorraría toneladas de espacio en la memoria de la aplicación.

También mover el deshacer al disco no debería causar un impacto negativo en el rendimiento porque no es algo que usará todo el tiempo. (Si se convierte en un cuello de botella, siempre puede crear un disco híbrido / sistema de memoria caché).

Extendido...

Si realmente está preocupado por el posible impacto del rendimiento causado por el almacenamiento de datos de deshacer en el sistema de archivos, puede considerar que el sistema de memoria virtual tiene buenas posibilidades de buscar estos datos en su archivo de página virtual de todos modos. Si crea su propio archivo de página / espacio de intercambio para estos archivos de deshacer, tendrá la ventaja de poder controlar cuándo y dónde se llama a la E / S del disco. No lo olvides, aunque todos deseamos que nuestras computadoras tuvieran recursos infinitos, son muy limitadas.

1.5GB (espacio de memoria de aplicación utilizable) / 32MB (tamaño de solicitud de memoria grande) ~ = 46


La mejor manera de hacerlo es como esta demostración del artículo, está en español, pero seguro que entiendes el código. http://www.nerdcoder.com/c-net-forzar-liberacion-de-memoria-de-nuestras-aplicaciones/

Aquí el código en el enlace del caso obtiene brock

using System.Runtime.InteropServices; .... public class anyname { .... [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)] private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int maximumWorkingSetSize); public static void alzheimer() { GC.Collect(); GC.WaitForPendingFinalizers(); SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); }

....

llamas a alzheimer() para limpiar / liberar memoria.


Puede implementar su propia clase de matriz que divide la memoria en bloques no contiguos. Digamos que tiene una matriz de 64 por 64 de [64,64] arreglos de ushort que se asignan y desasignan por separado. Luego simplemente haga un mapa a la derecha. La ubicación 66,66 estaría en la ubicación [2,2] en la matriz en [1,1].

Entonces, deberías poder esquivar el Heap de objetos grandes.


Si está asignando una gran cantidad de memoria en una biblioteca no administrada (es decir, memoria de la que el GC no tiene conocimiento), puede hacer que el GC lo conozca con el método GC.AddMemoryPressure .

Por supuesto, esto depende un poco de lo que está haciendo el código no administrado. Usted no ha declarado específicamente que está asignando memoria, pero me da la impresión de que sí lo está. Si es así, entonces esto es exactamente para lo que se diseñó ese método. Por otra parte, si la biblioteca no administrada está asignando mucha memoria, también es posible que esté fragmentando la memoria, que está completamente fuera del control del GC incluso con AddMemoryPressure . Espero que ese no sea el caso; si es así, probablemente tendrá que refactorizar la biblioteca o cambiar la forma en que se usa.

PD: no olvides llamar a GC.RemoveMemoryPressure cuando finalmente GC.RemoveMemoryPressure la memoria no administrada.

(PPS Algunas de las otras respuestas son probablemente correctas, es más probable que se trate simplemente de una pérdida de memoria en tu código, especialmente si se trata de procesamiento de imágenes, apostaría a que no estás eliminando correctamente tus instancias IDIsposable . en caso de que esas respuestas no te lleven a ningún lado, esta es otra ruta que podrías tomar).


Solo un aparte: el recolector de basura .NET realiza un GC "rápido" cuando una función regresa a su llamador. Esto eliminará los valores locales declarados en la función.

Si estructura su código de modo que tenga una función grande que asigna grandes bloques una y otra vez en un bucle, asignando cada bloque nuevo a la misma var local, es posible que el GC no intervenga para reclamar los bloques sin referencia durante algún tiempo.

Si, por otro lado, usted estructura su código de modo que tenga una función externa con un bucle que llama a una función interna, y la memoria se asigna y asigna a una var local en esa función interna, el GC debería activarse inmediatamente cuando el la función interna regresa a la persona que llama y recupera el bloque de memoria grande que se acaba de asignar, porque es una var local en una función que está regresando.

Evite la tentación de meterse con GC.Collect . GC.Collect explícitamente.


Use Process Explorer (desde Sysinternals) para ver cuál es el montón de objetos grandes para su aplicación. Su mejor apuesta será hacer que sus matrices sean más pequeñas pero tengan más de ellas. Si puede evitar asignar sus objetos al LOH, entonces no obtendrá OutOfMemoryExceptions y tampoco tendrá que llamar a GC.Collect manualmente.

El LOH no se compacta y solo asigna nuevos objetos al final del mismo, lo que significa que puede quedarse sin espacio con bastante rapidez.


puedes usar este método:

public static void FlushMemory() { Process prs = Process.GetCurrentProcess(); prs.MinWorkingSet = (IntPtr)(300000); }

tres formas de usar este método.

1 - después de disponer del objeto gestionado como clase, ....

2 - crea el temporizador con tales 2000 intervalos.

3 - crear hilo para llamar a este método.

Te sugiero que uses este método en el hilo o en el temporizador.