Net-Base Revista

23.08.2026

Localizar fugas de memoria: utilizar de forma dirigida FastMM FullDebugMode y leer correctamente las trazas de pila

FastMM FullDebugMode es, en proyectos Delphi, una de las herramientas más eficaces contra las fugas de memoria, pero solo si se activa de forma deliberada, se interpretan correctamente los informes y se evitan las suposiciones erróneas típicas. Este artículo práctico muestra un flujo de trabajo claro desde...

23.08.2026

Del tema de la revista a la práctica del proyecto

Páginas de servicios y técnicas relacionadas

Cuando una Delphi-aplicación en producción se «hincha» lentamente, falla esporádicamente con violaciones de acceso o se vuelve repentinamente inestable tras días de ejecución, a menudo no hay un bug aislado, sino un patrón: se solicita memoria que no se libera correctamente —o se libera demasiado pronto y luego se sigue usando. Aquí es donde FastMM FullDebugMode vale oro. No como estado permanente, sino como una herramienta de diagnóstico dirigida que convierte un «algo está roto en algún punto del heap» en una causa rastreable.

La trampa: FullDebugMode genera mucho output, penaliza el rendimiento y conduce con facilidad a interpretaciones erróneas. Un informe de fugas no muestra automáticamente el lugar donde está «el error». Y una traza de pila solo es tan buena como la resolución de símbolos (archivo MAP, información de depuración, inlining). En este artículo repaso el caso límite típico, explico el enfoque limpio y los escollos —para que al final no solo encuentres fugas, sino que las elimines de forma sostenible.

Cuándo tiene sentido realmente FastMM FullDebugMode

FastMM suele ser en versiones modernas de Delphi el gestor de memoria por defecto o ya está integrado en muchos proyectos. Sin embargo, el FullDebugMode es una configuración especial: marca los bloques de memoria con patrones de comprobación adicionales, registra trazas de pila de las asignaciones y verifica de forma más agresiva la corrupción del heap (es decir, datos de gestión dañados en el heap, p. ej. por desbordamientos de buffer).

Utilizo FullDebugMode de forma selectiva cuando se da alguna de estas situaciones:

  • Fuga reproducible: el consumo de memoria aumenta en la ejecución de prueba por operación (p. ej. por request, por importación, por acción de UI).
  • Violaciones de acceso esporádicas: especialmente las que ocurren «aquí y allá» en la misma zona (clásico: use-after-free).
  • Corrupción del heap: mensajes como «Invalid pointer operation», «Access violation in ntdll» o caídas al cerrar/finalizar.
  • Búsqueda de regresiones: tras refactorización, actualización de librería o cambio de compilador, nueva inestabilidad repentina.

No tiene sentido usar FullDebugMode como «lo activamos en todos los builds». El overhead es alto, el timing cambia y, precisamente, las condiciones de carrera pueden desaparecer o desplazarse. Para operación continua conviene más un monitoreo ligero (p. ej. working set del proceso, Private Bytes, contadores por operación): FullDebugMode es el bisturí, no el monitor de pulso.

Principio básico: el informe de fugas es un síntoma, la traza de pila es una pista

Un informe de fugas te muestra ante todo: estos bloques siguen asignados al final del programa. Eso solo es automáticamente un problema si esos bloques deberían haberse liberado. Existen «fugas» legítimas: singletons globales, cachés, handles del sistema operativo con duración de proceso o bibliotecas de terceros que deliberadamente no finalizan. Estos casos debes conocerlos, pero no corregirlos a ciegas.

La traza de pila en el informe muestra el lugar donde se solicitó el bloque. A menudo no es el sitio donde «se olvidó el Free». Realidad frecuente en sistemas evolucionados:

  • Asignación en la capa de UI o servicio, la liberación debería ocurrir en una capa más profunda (ownership poco claro).
  • Asignación en una factory, la ownership se transfiere al llamador —pero el llamador piensa que es «owned».
  • Los objetos se mantienen en colecciones (listas, diccionarios), pero el modelo de propiedad no es consistente.
  • Una ruta de excepción omite la limpieza porque falta try/finally o empieza demasiado tarde.

Por tanto, el procedimiento ordenado es: reproduciraislarresolver el Stacktraceencontrar errores de Ownershipcorrección con prueba de regresión. FastMM te proporciona las pistas, pero debes traducirlas a la arquitectura y a los ciclos de vida.

Activar correctamente FastMM FullDebugMode (sin pasar por alto efectos secundarios)

Gráfico esquemático con bloques de heap y bandas de comprobación convertidos en un informe de fugas
Resumen: FullDebugMode trabaja con bandas de comprobación adicionales y salida de informe.

En la práctica, el FullDebugMode se activa mediante las opciones de FastMM y una configuración de FastMM adecuada. Lo decisivo no es tanto «cómo se llama exactamente el archivo include», sino qué efecto tiene la configuración y bajo qué condiciones de build la usas.

Condiciones recomendadas para el build de depuración

  • Debug DCUs und Debug-Infos: Los Stacktraces solo son útiles si pueden resolverse a unidad/ línea/ dirección reales. Asegúrate de generar información de depuración y de disponer de un archivo MAP.
  • Elegir la optimización conscientemente: Para la legibilidad del Stacktrace, por lo general es mejor una compilación sin optimizar. El inlining y las optimizaciones agresivas pueden «desdibujar» los stackframes.
  • Condiciones de ejecución iguales: Utiliza, en lo posible, los mismos datos, la misma configuración y los mismos permisos. Muchas fugas dependen de los datos (p. ej., formatos raros, rutas especiales).
  • Separar 64-bit y 32-bit: El comportamiento de la memoria, el alineamiento y las bibliotecas de terceros difieren. Depura en la plataforma objetivo donde ocurre el problema.

Un punto que administradores y responsables técnicos a menudo subestiman: FullDebugMode también puede alterar la temporización. Si hay multihilo en juego, las condiciones de carrera pueden manifestarse de forma diferente. Por eso tiene sentido ejecutar en paralelo una pasada sin FullDebugMode que solo confirme la reproducción. FullDebugMode sería entonces el paso para el diagnóstico.

Precaución con „ReportMemoryLeaksOnShutdown“

Delphi puede informar fugas al finalizar el programa mediante ReportMemoryLeaksOnShutdown. Eso es práctico, pero en aplicaciones complejas (servicios, hosts de plug-ins, ejecuciones prolongadas) puede engañar: durante el shutdown se ejecutan secciones de finalización, los hilos se detienen, las cachés se limpian. Una fuga que es crítica a mitad de la ejecución puede desaparecer hasta el final —o, al contrario: una aparente fuga solo aparece en el shutdown porque todavía hay trabajo en segundo plano.

Para una caza de fugas práctica es más importante: medir la fuga por operación (p. ej., tras 100 requests), no solo al cerrar. FastMM puede ayudar, pero la configuración de la prueba debe reflejar eso.

El caso límite típico: el informe de fugas muestra «algún objeto», pero la causa es Ownership

Un clásico de las aplicaciones empresariales: un proceso de importación genera por registro objetos auxiliares (p. ej. StringLists, JSON-Parser, listas temporales). En el Happy Path se liberan correctamente. En casos raros (salto por validación, excepción, salida anticipada) queda un objeto colgado. Tras 10.000 registros se hace visible.

FastMM FullDebugMode ayuda aquí, porque muestra el punto de asignación. Pero la „solución“ no es „free en el lugar de la asignación“. La solución es un Ownership-Pattern robusto:

  • Quien crea un objeto no es automáticamente su Owner.
  • El ownership debe quedar claro en el contrato de la API (parámetros/valor devuelto, documentación, convenciones de nombres).
  • Las colecciones deben ser inequívocas: owning vs. non-owning. Las formas mixtas pasan factura.
  • Las rutas de excepción requieren bloques try/finally tempranos.

Si del stacktrace solo ves „TStringList.Create“, la información no es inútil — pero solo te dice: aquí se crea algo. La pregunta es: ¿dónde debería terminar? Y en eso ayuda más el pensamiento arquitectónico que la acrobacia del depurador.

Leer correctamente los Stacktraces: qué puedes deducir realmente

Detailaufnahme einer Debugging-Analyse mit unscharfem Debugger und handnotierter Call-Chain
En el stacktrace cuenta la cadena de llamadas – no la línea individual.

Un stacktrace de FastMM suele ser una lista de direcciones de retorno que — con símbolos de depuración — se mapean a Units, procedimientos y, idealmente, números de línea. Al leerlo, tres cosas son decisivas:

  • Top-of-Stack no siempre es el error: los frames superiores suelen pertenecer al Memory-Manager/RTL. Lo interesante es donde empieza tu código.
  • Cadena de llamadas en lugar de una sola línea: la línea es solo un punto. La cadena te muestra qué ruta condujo a la asignación.
  • Varios bloques idénticos: si FastMM informa varios leaks del mismo tamaño, suele tratarse de una ruta recurrente. Eso es bueno: tienes reproducibilidad.

Cuando faltan números de línea: MAP-Datei, Packages, Release-DCUs

Muchos equipos tropiezan aquí: FullDebugMode está activo, llega el informe de leaks, pero en lugar de Unit/línea solo hay direcciones o símbolos crípticos. Causas típicas:

  • No se generó archivo MAP ni información de depuración.
  • Estás ejecutando contra Release-DCUs o DLLs de terceros sin símbolos.
  • La aplicación utiliza Runtime Packages: entonces partes del código residen en BPLs, y la resolución de símbolos debe adaptarse a eso.
  • La optimización/Inlining ha hecho el stacktrace menos legible.

En la práctica esto significa: para la caza de leaks necesitas un build que sea deliberadamente „diagnosticable“. Es un objetivo distinto a „lo más rápido posible“. Los leads técnicos deberían tratarlo como un perfil de compilación propio, para que no cada miembro del equipo modifique opciones del proyecto ad hoc.

Evaluar frames: „Interesante“ suele estar una línea más arriba

Un ejemplo de la vida real (sin código de cliente concreto): la traza de pila te muestra como primer frame en tu código una rutina „LoadConfig“. Ahí ves una creación de objeto. Añades un Free, la fuga desaparece — y de repente salta en otro sitio un Double Free. ¿Por qué? Porque „LoadConfig“ coloca el objeto en un caché, y otra ruta de código ya es el propietario y lo libera más tarde.

La lectura correcta habría sido: la traza de pila te muestra dónde se origina el bloque. La corrección suele estar en la definición: ¿quién posee el objeto después del return? Si no respondes a esa pregunta de forma clara, solo cambias la manifestación del fallo (fuga → AV).

Corrupción de heap vs. fuga de memoria: por qué FullDebugMode a menudo detecta al verdadero culpable

Grafik, die einen Buffer-Overrun zeigt, der in benachbarten Speicherbereich überläuft
La corrupción del heap suele manifestarse con retraso: FullDebugMode la hace visible antes.

Muchas «fugas» son en realidad problemas secundarios: un Buffer-Overrun sobrescribe metadatos del heap, el gestor de memoria no puede liberar correctamente más tarde, y al final ves aparentemente fugas aleatorias u operaciones con punteros inválidos. FullDebugMode es eficaz aquí porque trabaja con patrones de comprobación y hace validaciones adicionales en Free/Reuse.

Es importante distinguir:

  • Fuga de memoria: el bloque se asignó y nunca se liberó. La estabilidad se degrada con el tiempo; no es obligatorio que haya un crash.
  • Use-after-free: el bloque se libera, pero luego se sigue utilizando. Conduce a AVs esporádicos que son difíciles de reproducir.
  • Double Free: el bloque se libera dos veces. Puede fallar inmediatamente o más tarde (cuando el bloque haya sido reutilizado).
  • Corrupción de heap: alguien escribe más allá de los límites de un bloque. Los síntomas suelen aparecer con retraso.

FullDebugMode es especialmente valioso cuando observas síntomas con retraso. La validación adicional hace que los errores sean visibles antes — a menudo exactamente en el punto donde ocurre el acceso incorrecto, no minutos después en un Free cualquiera.

Procedimiento en proyectos: caza reproducible de fugas en lugar de «depuración en la niebla»

Si quieres cazar fugas de memoria, necesitas un procedimiento que sea repetible y que pueda compartirse en equipo. Yo suelo trabajar con un marco de diagnóstico fijo:

1) Reproducción en un escenario determinista

Define una secuencia de prueba que muestre la fuga de forma fiable: „Inicia el servicio, procesa 500 mensajes, detén el servicio“ o „Abre la máscara X, ejecuta la acción Y 200 veces“. Es importante que documentes la secuencia con parámetros (conjunto de datos, cliente, feature-flags), para que otros puedan reproducirla.

2) Minimizar: hacer visible la fuga paso a paso

Si la secuencia dura 20 minutos, divídela. El objetivo es poder comparar lo antes posible entre „antes“ y „después“. En aplicaciones grandes eso suele ser el verdadero consumidor de tiempo, no tanto la corrección.

3) Activar FullDebugMode e interpretar el informe

Ahora entra en juego FastMM FullDebugMode. Recolecta los informes, agrúpalos por tamaño de bloque/Callstack y busca repeticiones. Un único bloque restante puede ser una caché legítima. 10.000 bloques idénticos son casi siempre una fuga de memoria real.

4) Aclaración de Ownership y corrección en la capa adecuada

Corrige las fugas en el lugar donde se define la Ownership: Factory, contrato de la API, Collection-Wrapper. „Poner un Free rápido“ justo junto a Create suele ser el lugar equivocado cuando el objeto se pasa más adelante.

5) Regresión: misma secuencia, mismo Build, mismo Report

La corrección solo es válida cuando la secuencia vuelve a ejecutarse y no aparecen ni fugas ni nuevos errores de memoria. Especialmente en casos de Use-after-free, que el „leak desaparezca“ no es una prueba, sino solo un nuevo síntoma.

Typische Fallstricke in Delphi-Code, die FastMM sichtbar macht

Collections und Ownership (Listas, Dictionaries, Interfaces)

Muchas fugas no provienen de algoritmos complejos, sino de estructuras de datos cotidianas. Dos patrones de error clásicos:

  • Una lista contiene objetos, pero nadie sabe quién los libera. Solución: usar una lista owning o vaciarla de forma consistente en el finally.
  • Un Dictionary mantiene objetos como Values; al Remove no se libera el Value o se olvida en el Clear.

Además son complicados los Interfaces: el recuento de referencias (similar a ARC) es cómodo, pero el funcionamiento mixto con objeto-Ownership puede generar fugas en caso de referencias cíclicas o eventos. FullDebugMode suele mostrarte entonces la ruta de asignación, pero la causa es un ciclo de referencias (A mantiene a B mediante Interface, B mantiene a A mediante Callback).

Exceptions y salidas tempranas

En sistemas de software empresarial evolucionados, las Exceptions suelen formar parte del control normal (p. ej. validación, abortos, Retry). El problema rara vez es la Exception en sí, sino la ruta alrededor: un objeto se crea antes del try/finally, luego ocurre una Exception y se omite el cleanup. FullDebugMode te proporciona el Stacktrace de la asignación — y debes verificar si existe un camino de liberación que se ejecute garantizado.

Threads y tiempo de vida: „Freigeben im falschen Thread“

Con VCL/FMX y servicios con Worker-Threads surge otro caso límite: un objeto se crea en un thread, pero se libera en el UI-Thread (o al revés), porque se pasa „rápidamente“ algo vía Queue/Synchronize. Eso puede funcionar, pero también puede causar Use-after-free si el Producer sigue trabajando mientras el Consumer ya libera.

FastMM FullDebugMode puede ayudar aquí, ya que detecta antes errores retardados en el tiempo. La corrección real es, sin embargo, un modelo de tiempo de vida limpio: relaciones de propiedad claras, transferencia solo mediante datos immutable o puntos de transferencia de Ownership inequívocos.

Cómo hacer útiles los Reports: filtrar, comparar, documentar

En equipos merece la pena no solo „mirar“ los Leak-Reports, sino tratarlos como artefactos. Tres medidas pragmáticas que han demostrado su eficacia:

  • Baseline-Report: Un „estado conocido“ (p. ej. la versión de producto actual) se ejecuta una vez con FullDebugMode y se guarda como referencia. Así detectas nuevas fugas al instante.
  • Comparación por Use-Case: Para flujos críticos (Import, Export, API-Request, operaciones masivas en UI) defines una secuencia breve específica que pueda repetirse regularmente.
  • Fugas „legítimas“ documentadas: Si una caché no se finaliza conscientemente, documenta eso. Si no, dentro de seis meses alguien volverá a perseguir las mismas entradas.

Esto no es burocracia, sino un ahorro de tiempo: de lo contrario, la búsqueda de fugas de memoria se convierte rápidamente en un bucle sin fin, porque los mismos patrones reaparecen en cada sprint.

Cuándo merece la pena el esfuerzo — y cuándo deberías proceder de otra forma

FastMM FullDebugMode es una herramienta de diagnóstico con coste. El esfuerzo merece la pena especialmente cuando:

  • La aplicación se ejecuta durante mucho tiempo (servicio, cliente de Terminal Server, sistema por turnos, procesos 24/7).
  • Procesas flujos de datos reales de clientes y no cubres todas las rutas en las pruebas.
  • La estabilidad es más importante que la velocidad de entrega de funcionalidades a corto plazo (típico en soluciones de software cercanas al proceso).

Si, en cambio, solo tienes un pequeño asistente de escritorio que finaliza tras 30 segundos, la búsqueda de fugas suele ser secundaria. Igualmente: si tienes un pico de memoria puntual (p. ej., una exportación grande), a menudo no se trata de una fuga, sino de una cuestión de estrategia de streaming y de la carga máxima en el heap.

Conclusión práctica: FullDebugMode no es un interruptor, sino un proceso

FastMM FullDebugMode aporta estructura a la búsqueda de errores de memoria: hace visibles las asignaciones, detecta antes la corrupción del heap y proporciona trazas de pila con las que puedes arreglar la causa en vez del síntoma. Sin embargo, la palanca determinante no es la herramienta, sino el proceso: escenarios reproducibles, builds diagnósticos, contratos claros de ownership y regresión frente a una baseline.

Si te encuentras atascado con una fuga persistente o con errores esporádicos del heap y quieres estabilizar de forma duradera el asunto en un sistema Delphi más grande, merece la pena un setup de diagnóstico breve y limpio con una secuencia clara y reportes evaluables. Si necesitas apoyo en análisis, perfiles de build o refactorización de arquitectura: contacto con Net-Base Software GmbH.

Para este tema también son importantes Delphi Encontrar fugas de memoria y Leer el Fastmm Leak Report. El artículo contextualiza estos aspectos de forma comprensible y muestra qué es importante en la práctica cotidiana.

Discutir proyecto o iniciativa de modernización con Net-Base.

siguiente paso

Cuando un tema se convierte en un proyecto real, arquitectura, entorno existente y operación deben considerarse conjuntamente desde el inicio.

No solo apoyamos en consultas puntuales, sino también cuando, a partir de fragmentos de código fuente, temas heredados o ideas de portales, debe consolidarse un proyecto empresarial robusto.

  • La situación actual, el estado objetivo y los riesgos técnicos se evalúan conjuntamente.
  • REST, el acceso a datos, los portales y el despliegue no se relegan a fases posteriores.
  • Usted detecta con antelación qué camino es viable, tanto económica como operativamente.

Compartir entrada

Compartir esta publicación directamente

LinkedIn, X, XING, Facebook, WhatsApp y correo electrónico están disponibles de inmediato. Para Instagram preparamos el enlace y el texto breve directamente.

Correo electrónico

Instagram se abre en una nueva pestaña. El enlace y el texto breve se copian previamente en el portapapeles.