2011-03-10 21 views
7

Tengo una aplicación de GUI que no tiene tiene una pérdida de memoria. Lo he confirmado con FastMM en numerosos ciclos de prueba. En el servidor de un cliente en particular, recibo bloqueos aleatorios. Las especificaciones del servidor están en línea con las de nuestros otros clientes (y en realidad hemos probado varios tipos de hardware), al igual que los archivos utilizados por el programa (hasta donde sé, hay un material súper sensible que no se puede acceder, pero no parece haber nada fuera de lo común allí).Delphi: compruebe si la memoria se está liberando "a tiempo"

He probado los gustos de EurekaLog y MadShi, para quizás reducir el problema, pero desafortunadamente, solo parecen detectar una excepción en el momento del choque en ocasiones, no todo el tiempo. Cuando lo hace, generalmente muestra uno o más errores de "Memoria insuficiente" antes del bloqueo.

Así que estoy pensando que tal vez algunos objetos se liberen "demasiado tarde", es decir, solo cuando la aplicación se cierre, en lugar de cuando quiero liberarlos? He visto la demo de FastMMUsageeTracker, pero no pude entender todo. ¿Hay documentación en alguna parte? ¿O podría alguien poner palabras (algo accesibles) sobre cómo podría verificar esto?

Alternativamente, ¿cuál sería el mejor enfoque para detectar que la aplicación se está acercando a su "límite de memoria", para tomar algunas medidas preventivas? Si lo entiendo correctamente, una aplicación Delphi regular es de 32 bits, debería ser un buen manejo de hasta 2Gb de memoria (siempre que el hardware lo admita, por supuesto), ¿correcto?

PS: Delphi XE 2009 o, si eso es relevante

Gracias!

EDITAR - Edición posiblemente resuelto

Hemos sido capaces de encontrar un problema por el que una ventana emergente que se cierra y se libera automáticamente después de un tiempo se estaba creando a un ritmo mucho más rápido de lo que estaba desapareciendo. Esto consumiría una gran cantidad de memoria con el tiempo, y luego cualquier asignación de memoria básicamente lo sobrepasaría y desencadenaría el problema de "falta de memoria".

Esto explicaría por qué las trazas de la pila son inconsistentes.

No estoy del todo convencido de que este sea nuestro único problema, ya que, aunque es poco probable, este escenario bien podría haber sucedido antes en los años en que nuestra aplicación se ha ejecutado, pero de alguna manera no es así. Haré muchas más investigaciones sobre este tema.

Gracias a todos los que respondieron, cada respuesta en realidad tiene información valiosa.

+1

PS Las excepciones a la memoria insuficiente pueden aparecer desde aproximadamente 1 GB hacia arriba; no hay un nivel predefinido. Hay muchos factores que parecen influir en el umbral exacto: memoria RAM total, memoria virtual total, cuánto se usan en otros procesos, etc. ¡Además, mi código siguiente es originalmente del rastreador de memoria FastMM! – Misha

+1

Una cosa que vale la pena destacar: nunca he logrado agotar la memoria disponible en proyectos en los que he trabajado, pero he salido de errores de memoria algunas veces. Puede ser causado en ciertos casos por datos corruptos o mal calculados que intentan pedirle al administrador de memoria que asigne un único búfer de unos pocos gigabytes de tamaño cuando todo lo que realmente necesita es unos pocos K o menos. –

Respuesta

7

Si tiene Delphi XE, viene con AQTime, y AQTime tiene un perfilador de asignación de memoria como parte de su bolsa de trucos. Si lo ejecuta en su programa, es posible que pueda ver hacia dónde va su RAM.

+1

Gracias Mason. Intenté eso (tenía grandes esperanzas), y también probé la prueba de la versión Pro. Por alguna razón, parece que no puedo hacer que el generador de perfiles de asignación funcione. Por el contrario, parece colgar en el momento en que solicito los resultados (lo dejé "obteniendo los resultados" durante más de una hora hoy - la aplicación tenía un conjunto de trabajo de aproximadamente 100Mb en caso de que fuera relevante), y nunca pasé el "intento de obtener resultados" mensaje). Hace lo mismo con las versiones independientes y las integradas. Puedo hacer que el perfilador de rendimiento funcione sin problemas, pero no el de asignación. – Bourgui

+0

Actualización: logré utilizar la versión Pro independiente. Olvidé generar el seguimiento de pila cuando construí mi aplicación, y ahora puedo usar el generador de perfiles de asignación. Me señaló una ventana emergente cronometrada que se llamaba cientos o miles de veces, demasiado rápido para ser compensado por el hecho de que se cerró automáticamente. Sin embargo, aún no se pueden obtener resultados de la versión integrada IDE (???). – Bourgui

+0

Marcado como respuesta ya que así es como encontré el problema emergente. – Bourgui

3

Puede averiguar cuánta memoria está usando su aplicación - vea esta página About. Resumen:

uses PsAPI; 

//current memory size of the current process in bytes 
function CurrentMemoryUsage: Cardinal; 
var 
    pmc: TProcessMemoryCounters; 
begin 
    pmc.cb := SizeOf(pmc) ; 
    if GetProcessMemoryInfo(GetCurrentProcess, @pmc, SizeOf(pmc)) then 
    Result := pmc.WorkingSetSize 
    else 
    RaiseLastOSError; 
end; 
ShowMessage(FormatFloat('Memory used: ,.# K', CurrentMemoryUsage/1024)) ; 

Si registra ese valor periódicamente en su servidor, al menos tendrá una idea de lo que está sucediendo. Hay más información en ese resultado que debería ayudarlo a aprender más sobre lo que su programa está haciendo.

La solución será ver qué es lo que realmente está usando la memoria y administrarla de forma más agresiva.Sospecho que habrá algún lugar en el que cree objetos y solo los libere al apagarlos, cuando pueda (y debería) liberarlos tan pronto como haya terminado con ellos.

Una posible solución es usar el modificador/3GB en la versión completa de FastMM y ver si el problema tarda más.

Si tiene una mala suerte espectacular, habrá "roto" el algoritmo de administración de la agrupación de memoria de FastMM para que nunca libere memoria (a related question). Probar diferentes administradores de memoria puede ayudarlo, ya que algunos son más agresivos a la hora de reclamar la memoria no utilizada. Pero si estás fragmentando tu montón, la única solución real es resolver cómo evitar hacerlo. Que es un tema complejo, así que de nuevo: primero prueba las cosas simples de arriba.

+0

Gracias Moz. Empecé a jugar con TProcessMemoryCounters. Sin embargo, ¿esto no proporciona "solo" memoria de conjunto de trabajo, también conocida como memoria RAM? Solo estoy adivinando aquí, pero ¿no sería un error de "Falta de memoria" sugerir que es la memoria virtual general demasiado grande? Corrígeme si estoy equivocado. Probaré el modificador/3G. – Bourgui

+0

@Bourgui, "sin memoria" es un mensaje bastante amplio, y una máquina moderna debería tener (mucho) más de 2 GB de memoria física, por lo que no debería estar agotando la memoria virtual. La estructura de ProcessMemoryCounters tiene otra información, registraría todo y vería qué cambios. El registro también lo ayudará a averiguar dónde exactamente sale todo mal. Sugiero también mirar los rastros de la pila de sus excepciones. En el peor de los casos, es posible que deba escribir un registrador externo si descubre que el registro falla debido a la condición de falta de memoria. Pero evita eso hasta que lo necesites. –

+0

gracias por los comentarios. Eso es lo que pensé también.Desafortunadamente, la mayoría de mis rastros de pila no me llevaban a ningún lado, y eran en su mayoría inconsistentes. Desde entonces pude aislar un problema (ver mi edición OP) - con suerte es la fuente de todos mis problemas. – Bourgui

1

Cuando recibí un error de "Falta de memoria", fue debido a un bucle fuera de control. Normalmente, el bucle asignará memoria y no se detendrá antes de que se haya utilizado toda la memoria disponible. Liberar la memoria no era un problema, porque el programa nunca llega a ese punto. Tipos de código que me han sido mordidos son:

A “mientras que no x.Eof hacer” bucle sin un “x.Next” para avanzar a través del conjunto de datos, o

un procedimiento recursivo o subrutina que nunca se encontró la (s) condición (es) de salida.

Buscaría cualquier tipo de ciclo o recursión que podría continuar "para siempre" en ciertas circunstancias, como construir una estructura de datos en la memoria que es masiva.

+0

Gracias crefird, pero no creo que ese sea el caso aquí. Ver mi edición para obtener más información sobre lo que hemos encontrado hasta ahora. – Bourgui

6

Olvídese de la memoria "Windows": lo que desea es la memoria real asignada por la aplicación. Esta es la única forma en que puede saber si está asignando memoria que no se libera con el tiempo. Para Delphi 2006+ con FastMM, esto es lo que necesita:

//------------------------------------------------------------------------------ 
// CsiGetApplicationMemory 
// 
// Returns the amount of memory used by the application (does not include 
// reserved memory) 
//------------------------------------------------------------------------------ 
function CsiGetApplicationMemory: Int64; 
var 
    lMemoryState: TMemoryManagerState; 
    lIndex: Integer; 
begin 
    Result := 0; 

    // get the state 
    GetMemoryManagerState(lMemoryState); 

    with lMemoryState do begin 
    // small blocks 
    for lIndex := Low(SmallBlockTypeStates) to High(SmallBlockTypeStates) do 
     Inc(Result, 
      SmallBlockTypeStates[lIndex].AllocatedBlockCount * 
      SmallBlockTypeStates[lIndex].UseableBlockSize); 

    // medium blocks 
    Inc(Result, TotalAllocatedMediumBlockSize); 

    // large blocks 
    Inc(Result, TotalAllocatedLargeBlockSize); 
    end; 
end; 

me conecto esto en un intervalo (en cualquier lugar entre 10 segundos y 10 minutos) a mi archivo de registro, junto con la diferencia de la última vez.

+0

+1 Algo como este es mi primer paso también. Si excluye todo lo demás, primero descubra si su aplicación está acaparando memoria en alguna lista, o si se trata de una fragmentación de pantalla "real". –

+0

Gracias Misha, todavía no lo he intentado pero parece interesante, lo intentaré, antes de mirar solo los bloques pequeños. – Bourgui

2

¿Puede mostrarnos un seguimiento de pila cuando obtiene el error? ¿Cuánta memoria consume en el momento del error?

Hace un tiempo hice un registrador de memoria (para FastMM) que registra toda la memoria entre 2 puntos (usando un procedimiento "startlog" y "endlog") para encontrar una "fuga blanda": estaba asignando objetos en una lista pero nunca borrando la lista, solo cuando se cierra la aplicación, por lo que FastMM informó que no hubo filtración. Al usar mi registrador de memoria pude encontrar esos objetos (solo registra la nueva memoria asignada que no se libera antes de ejecutar el procedimiento "endlog").
Veré si puedo encontrar este código.

Por cierto: se puede obtener una "memoria" en otros 3 maneras:

  • FastMM da este error cuando se detecta un error al asignar. Por lo tanto, no es un verdadero "falta de memoria", sino más bien un error fastmm interno (debido a la corrupción, etc.)
  • Alloc un bloque muy grande (por ejemplo, 1 Gb). Una vez lo recibí debido a un error de lectura de flujo (RemObjects) por lo que leí un valor de tamaño incorrecto para una cadena, por lo que trató de preasignar una cadena grande (aleatoria). Tal error parece raro porque en mi caso mi aplicación había asignado aproximadamente 150Mb así que tampoco tenía memoria "sin memoria"
  • Fragmentación: si intentas asignar un bloque de 10Mb pero Windows no puede encontrar un bloque contínuo de 10Mb, entonces Windows dar un "fuera de la memoria".

¡Por favor, proporcione un rastro de pila y la cantidad de memoria utilizada en el momento del error!

+0

Hola André, gracias por responder. No publiqué un seguimiento de pila porque todos eran diferentes. Sin embargo, es posible que hayamos encontrado el problema, vea mi edición. – Bourgui

2

Con la limitación de Delphi al espacio de direcciones de 32 bits, tales problemas son cada vez más comunes.

Lo primero y más simple que puede hacer es ejecutar en un sistema operativo de 64 bits y pasar de un espacio de direcciones disponible de 2 GB (como se obtiene en un sistema operativo de 32 bits) a una dirección de 4 GB. Esto no sucede automáticamente Debe marcar su aplicación como LARGEADDRESSAWARE. Para ello, agregue lo siguiente a su archivo .dpr:

const 
    IMAGE_FILE_LARGE_ADDRESS_AWARE = $0020; 

{$SetPEFlags IMAGE_FILE_LARGE_ADDRESS_AWARE} 

La causa común de errores de memoria no es que hay una shorage de la memoria, sino que están pidiendo un gran bloque de memoria contigua y no hay un solo bloque contiguo de espacio de direcciones disponible.

Hacer frente a este problema es más difícil. Primero necesita identificar las partes de su código que actualmente exigen grandes bloques contiguos de memoria. A continuación, debe modificar los objetos que lo están haciendo y organizar que, en su lugar, soliciten pequeños trozos de memoria que luego puede "unir" para dar la apariencia de un bloque más grande. Esto generalmente ocurre con el código que usa matrices dinámicas, en mi experiencia.

+0

Gracias David. Pregunta: ¿Cuál es la diferencia entre eso y la bandera/3G con FastMM? Como mencioné en mi edición, he encontrado un problema que podría ser la fuente de mis problemas, y dado que la mayoría de las asignaciones de memoria son relativamente pequeñas (no superan los 10s de Mb) y nuestra aplicación se ejecuta en un sistema eso no se usa para nada más, dudo (¡y espero!) que sea la causa aquí. Sin mencionar, tiendo a alejarme de las matrices dinámicas siempre que puedo. – Bourgui

+0

@Bourgui 10s de Mb, repetidamente podría ser un problema./3G es la versión de 32 bits. Aún necesita marcar su exe como LARGEADDRESSAWARE. Entonces necesita iniciar ventanas con/3GB. ¿Alguien hace esto? De todos modos, seguramente el servidor de su cliente es una máquina de 64 bits. ¿Marcas tu aplicación como LARGEADDRESSAWARE? ¿Si no, porque no? –

+0

tales asignaciones de memoria grandes son relativamente pocas y distantes entre sí en la aplicación, eso es lo máximo que puede obtener. También son persistentes en el tiempo. Esas asignaciones también están bien registradas y deberíamos poder notar esos problemas con relativa facilidad. Es por eso que no creo que ese sea el problema. Pero aún no estoy descartando nada por completo. En verdad, nunca hemos tenido problemas de memoria hasta ahora, por lo que nunca hemos tenido un incentivo para hacer nuestra aplicación LargeAddressAware, pero definitivamente es algo que debemos analizar: no puedo contener la respiración por un compilador Delphi de 64 bits. ! ; o) – Bourgui

Cuestiones relacionadas