Pregunta Problemas de memoria .NET cargando ~ 40 imágenes, memoria no reclamada, potencialmente debido a la fragmentación de LOH


Bueno, esta es mi primera incursión en la creación de perfiles de memoria de una aplicación .NET (ajuste de la CPU que he hecho) y estoy llegando a un poco de un muro aquí.

Tengo una vista en mi aplicación que carga 40 imágenes (máx.) Por página, cada una corriendo aproximadamente ~ 3MB. El número máximo de páginas es 10. Como no quiero mantener 400 imágenes o 1.2GB en la memoria a la vez, configuro cada imagen como nula cuando se cambia la página.

Ahora, al principio pensé que solo debía tener referencias obsoletas a estas imágenes. Descargué ANTS profiler (gran herramienta BTW) y realicé algunas pruebas. El gráfico de duración del objeto me dice que no tengo ninguna referencia a estas imágenes aparte de la referencia única en la clase principal (que, por diseño, también se confirma al peinar minuciosamente mi código):

enter image description here

La clase de padres SlideViewModelBase se queda para siempre en un caché, pero el MacroImage la propiedad se establece en nulo cuando se cambia la página. No veo ninguna indicación de que estos objetos se mantengan por más tiempo de lo esperado.

A continuación, eché un vistazo al gran montón de objetos y al uso de la memoria en general. Después de mirar tres páginas de imágenes, tengo 691.9MB de memoria no administrada asignada y 442.3MB en el LOH. System.Byte[], que viene de mi System.Drawing.Bitmap a BitmapImage la conversión está tomando casi todo el espacio LOH. Aquí está mi código de conversión:

public static BitmapSource ToBmpSrc( this Bitmap b )
{
    var bi = new BitmapImage();
    var ms = new MemoryStream();
    bi.CacheOption = BitmapCacheOption.OnLoad;
    b.Save( ms,  ImageFormat.Bmp );
    ms.Position = 0;
    bi.BeginInit();
    ms.Seek( 0, SeekOrigin.Begin );
    bi.StreamSource = ms;
    bi.EndInit();
    return bi;
}

Me está costando encontrar dónde va toda esa memoria no administrada. Sospechaba que System.Drawing.Bitmap objetos al principio, pero ANTS no los muestra pegados, y también realicé una prueba en la que me aseguré de que todos fueran eliminados y no hizo la diferencia. Así que aún no he descubierto de dónde viene toda esa memoria no administrada.

Mis dos teorías actuales son:

  1. Fragmentación LOH Si navego fuera de la vista paginada y hago clic en un par de botones, se recupera aproximadamente la mitad de los ~ 1.5GB. Todavía demasiado, pero interesante, no obstante.
  2. Algo extraño de WPF vinculante. Usamos el enlace de datos para mostrar estas imágenes y no soy un experto en lo que respecta a los pormenores de cómo funcionan estos controles WPF.

Si alguien tiene alguna teoría o consejos sobre el perfil, estaría muy agradecido ya que (por supuesto) estamos en un plazo apretado y estoy luchando un poco para terminar esta parte final y funcionando. Creo que me he echado a perder rastreando filtraciones de memoria en C ++ ... ¿quién hubiera pensado?

Si necesita más información o desea que intente algo más, pregunte. Perdón por el muro de texto aquí, traté de mantenerlo lo más conciso posible.


32
2018-06-07 21:32


origen


Respuestas:


Esta entrada en el blog parece descifrar lo que está viendo, y la solución propuesta era crear un implementación de Stream que envuelve otra secuencia.

El método Dispose de esta clase contenedora debe liberar la secuencia envuelta, de modo que pueda ser recolectada como basura. Una vez que la BitmapImage se inicializa con esta secuencia contenedora, la secuencia contenedora puede eliminarse, liberando la secuencia subyacente y permitiendo que se libere la gran matriz de bytes.

BitmapImage guarda una referencia a la secuencia fuente para mantener vivo el objeto MemoryStream. Desafortunadamente, aunque se ha invocado el objeto MemoryStream.Dispose, no libera la matriz de bytes que envuelve la secuencia de la memoria. Entonces, en este caso, el mapa de bits hace referencia al flujo, que hace referencia al búfer, que puede ocupar mucho espacio en el gran montón de objetos. No hay una verdadera pérdida de memoria; cuando ya no hay más referencias al mapa de bits, todos estos objetos serán (eventualmente) basura recolectada. Pero dado que el mapa de bits ya ha hecho su propia copia privada de la imagen (para renderizar), parece bastante inútil tener la copia original ahora innecesaria del mapa de bits aún en la memoria.

Además, ¿qué versión de .NET estás usando? Antes de .NET 3.5 SP1, había un problema conocido en el que BitmapImage podría causar una pérdida de memoria. La solución fue llamar Congelar en la BitmapImage.


35
2018-06-07 21:45



¿Dónde estás cerrando y eliminando la corriente de la memoria? Podría ser que el GC tenga que trabajar mucho más para liberar los recursos moviendo varias generaciones antes de ejecutar los destructores en el objeto (lo que generalmente se llama deshacer si se olvidó de hacerlo).

En su caso, no puede disponer de la secuencia de memoria hasta que haya terminado con la imagen. Cuando desee que se descarguen, recorra las imágenes e intente eliminar la secuencia de la memoria.


2
2018-06-07 21:41