2011-05-19 12 views
7

Tengo la tarea de mejorar un código que genere informes masivos, de la manera que considere más conveniente.Diagnóstico de .NET OutOfMemoryException al generar informes

Hay alrededor de 10 informes idénticos generados (para cada 'sección' de la base de datos), y el código para ellos es similar a esto:

GeneratePurchaseReport(Country.France, ProductType.Chair); 
GC.Collect(); 
GeneratePurchaseReport(Country.France, ProductType.Table); 
GC.Collect(); 
GeneratePurchaseReport(Country.Italy, ProductType.Chair); 
GC.Collect(); 
GeneratePurchaseReport(Country.Italy, ProductType.Table); 
GC.Collect(); 

Si quito esos GC.Collect() llamadas, se bloquea el servicio de informes con OutOfMemoryException.

La mayor parte de la memoria se guarda en un enorme List<T> que está lleno dentro de GeneratePurchaseReport y ya no se usa tan pronto como sale, por lo que una colección completa de GC recuperará la memoria.

Mi pregunta es doble:

  1. ¿Por qué el GC hacer esto por sí solo? Tan pronto como se esté quedando sin memoria en el segundo GeneratePurchaseReport, debería hacer una colección completa antes de colgar y grabar, ¿no es así?
  2. ¿Hay algún límite de memoria que pueda aumentar de alguna manera? No me importa en absoluto si los datos se intercambian en el disco, ¡pero el proceso .net está utilizando mucha menos memoria que incluso los 2.5 GB de RAM disponibles! Esperaría que solo fallara si se queda sin espacio de direcciones, pero en una máquina de 64 bits dudo que eso pase tan pronto.
+1

Bueno, cuando miré solo el título, pensé que necesitabas algún tipo de aplicación de estrés que intente grabar memoria con fines de prueba. –

+0

¿Qué es *** GeneratePurchaseReport ***? ¿Informe local RDLC o RDL remoto en servidor SSRS? – Kiquenet

+0

** GcHelper ** https://github.com/mcctomsk/MccTomskHelpers/blob/560a079172468fd44ce952fbfcd676d297602442/Core/GcHelper.cs Notas: http://stackoverflow.com/questions/10016541/garbage-collection-not-happening-even cuando necesitábamos y _La razón por la que se llama a GC.Collect dos veces: http://stackoverflow.com/questions/3829928/under-what-circumstances-we-need-to-call-gc-collect-twice_ – Kiquenet

Respuesta

3

Tendríamos que ver su código para estar seguro.

De no ser así:

  • ¿Está pre-dimensionamiento de la lista con un número esperado de artículos?

  • ¿Se puede asignar previamente y utilizar una matriz en lugar de una lista? (Boxeo/unboxing podría ser entonces un coste adicional)

  • Incluso en una máquina de 64 bits, el tamaño más grande un solo objeto CLR puede ser es de 2 GB

  • pre-asignar una MemoryStream para mantener el informe completo, y escribe a eso.

De interés ?:

se recomienda usar un perfilador de memoria como memprofiler o Redgate (ambos tienen pruebas gratuitas) para ver dónde yace el problema en realidad).

5

leer sobre el montón de objetos grandes.

Creo que lo que ocurre es que el documento final para informes individuales se construye y anexa a lo largo del tiempo, de modo que en cada operación de adición se crea un documento nuevo y se descarta el anterior (probablemente detrás de las escenas). Este documento es (eventualmente) mayor que el umbral de 85,000 bytes para el almacenamiento en el Heap de objetos grandes.

En este escenario, en realidad no está usando mucha memoria física —, todavía está disponible para otros procesos. Lo que está usando es espacio de direcciones que está disponible para su programa. Cada proceso en Windows tiene su propio (típicamente) espacio de direcciones de 2GB disponible. Con el tiempo, a medida que asigna nuevas copias de su documento de informe en crecimiento, deja numerosos agujeros en el LOH cuando se recopila la copia anterior. La memoria liberada por objetos anteriores ya no se usa y está disponible para otros procesos, pero el espacio de direcciones aún se pierde; está fragmentado y necesita ser compactado. Finalmente, este espacio de direcciones se llena y obtienes una excepción de OutOfMemory.

La evidencia sugiere que las llamadas a GC.Collect() permiten cierta compactación de LOH, pero no es una solución perfecta. Casi todo lo demás que he leído sobre el tema indica que GC.Collect() no debe compactar el LOH en absoluto, pero he visto varios informes anecdóticos (algunos aquí en Stack Overflow) donde se llama a GC.Collect() de hecho fue capaz de evitar excepciones de OutOfMemory de la fragmentación de LOH.

Una solución "mejor" (en términos de estar seguro de que nunca se quedará sin memoria, usar GC.Collect() para compactar el LOH simplemente no es confiable) es dividir su informe en unidades más pequeñas de 85000 bytes, y escríbalos todos en un único búfer al final, o usando una estructura de datos que no descarta su trabajo anterior a medida que crece. Desafortunadamente, es probable que sea mucho más código.

Una opción relativamente simple aquí es asignar un búfer para un objeto MemoryStream que sea más grande que su informe más grande, y luego escribir en el MemoryStream a medida que crea el informe. De esta manera nunca dejas fragmentos. Si esto se acaba de escribir en el disco, es posible que incluso vaya directamente a FileStream (tal vez a través de TextWriter, para que sea más fácil cambiarlo más adelante). Si esta opción resuelve su problema, me gustaría escuchar sobre él en un comentario a esta respuesta.

+0

Todo lo que He leído sugiere que el LOH nunca se compacta? Los objetos son basura recogida en LOH (durante la colección Gen 2) (y presumiblemente regiones adyacentes libres concatenadas), pero no compactadas, afaik. –

+1

http://msdn.microsoft.com/en-us/magazine/cc534993.aspx –

+1

@Mitch - no es un afaik compactado, pero he visto varias instancias ahora en las que GC.Collect() pudo corregir errores aparentes Fragmentación de LOH, así que estoy empezando a preguntarme si una actualización o parche ha vuelto obsoletos a estos artículos. –

0

El motivo probablemente sea el Montón de objetos grandes y cualquier objeto que utilice el almacenamiento dinámico nativo internamente, p. Clase de mapa de bits El montón de objetos grandes también es un montón de C tradicional, que se fragmenta. La fragmentación es un aspecto de este problema.

Pero creo que también tiene algo que ver con la forma en que GC determina cuándo recolectar. Funciona perfectamente para los montones generacionales normales, pero para la memoria asignada en otros montones, especialmente para la memoria en montones nativos, puede no tener suficiente información para tomar una decisión perfecta. Y LOH se trata como generación 2, lo que significa que tiene la menor posibilidad de ser recolectado.

Entonces, en su caso, creo que el forzado manual es una solución razonable. Pero sí, no es perfecto.

PD: Me gustaría añadir un poco más de información a la buena explicación de Joel. El umbral para LOH es de 85000 bytes para objetos normales, pero para la matriz doble es de 8000 bytes.

-3

En primer lugar, la recolección de basura se ejecuta en 1 supuesto: la capacidad del montón es ilimitada. Recolectores de basura no recoge objetos cuando se queda sin memoria, pero recoge objetos si hay algún objeto que el programa ya no usa. Depende de los algoritmos de GC, creo que GC indica que la memoria utilizada para el informe todavía se está utilizando. Por lo tanto, no puede simplemente eliminarlo.

La razón por la cual el GC no hace su trabajo cuando se llama al GeneratePurchaseReport() es porque GC no se está ejecutando todo el tiempo. Emplea cierto algoritmo para predecir con qué frecuencia debe recogerse la basura en función del comportamiento anterior. Y en su caso, ciertamente no predice que la basura deba ser recolectada en 4 líneas consecutivas.

+3

esto está mal en varios puntos. –

+1

Si la capacidad del almacenamiento dinámico fuera ilimitada, GC no sería necesario. Yo diría que la existencia misma de GC se basa en la suposición de que el montón * está * limitado. – cHao

Cuestiones relacionadas