Tutorial completo de memory leaks en Linux

Última actualización: 14/05/2026
Autor: Isaac
  • Las fugas de memoria en Linux degradan el rendimiento de forma silenciosa y acaban activando el OOM Killer si no se detectan a tiempo.
  • Herramientas como top, htop, /proc, pmap y smem permiten localizar procesos sospechosos y analizar cómo crece su consumo de memoria.
  • Valgrind, memleax, gdb y otros perfiladores ayudan a identificar el origen exacto de los leaks y los errores de gestión de memoria en el código.
  • Pruebas de carga, límites de recursos y buenas prácticas de programación son clave para prevenir fugas serias en entornos de producción.

tutorial memory leaks en linux

Si alguna vez has tenido un grifo del fregadero que pierde agua poco a poco, ya sabes lo traicioneras que pueden ser las fugas: al principio no parecen gran cosa, pero con el tiempo te montan un buen lío. Con las fugas de memoria en Linux pasa exactamente lo mismo: empiezan como un goteo casi imperceptible y acaban tirando abajo un servicio, un microservicio crítico o incluso todo un servidor.

En un sistema que debe estar siempre disponible (servidores, contenedores en producción, dispositivos embebidos, etc.), una fuga de memoria que nadie vigila es como una bomba de relojería. Si no monitorizas, no detectas y no corriges, terminarás lidiando con procesos matados por el OOM Killer, caídas aleatorias y usuarios cabreados sin entender muy bien qué ha pasado. En este tutorial vamos a ver de forma detallada, pero con un lenguaje cercano, cómo detectar y analizar memory leaks en Linux usando desde herramientas básicas como top/htop hasta perfiladores avanzados como Valgrind, memleax o gdb, además de utilidades como /proc, pmap o smem.

Qué es una fuga de memoria en Linux y por qué es tan peligrosa

Una fuga de memoria se produce cuando un programa reserva memoria del sistema (heap, estructuras, buffers, etc.) y nunca la libera cuando ya no la necesita. En lenguajes como C o C++ esto suele traducirse en llamadas a malloc, calloc, new que no van acompañadas de sus correspondientes free o delete, o en referencias que se quedan colgadas y hacen imposible reutilizar esa memoria.

En la práctica, el proceso continúa ejecutándose con normalidad, pero su consumo de memoria crece de forma paulatina con cada petición, cada ciclo de trabajo o cada nueva tarea. Ese crecimiento puede ser muy lento (del estilo de unos pocos KB o MB al día), lo que lo vuelve especialmente difícil de detectar a simple vista si no tienes una buena monitorización.

Las consecuencias de ignorar este problema son claras: degradación del rendimiento, swaps constantes, latencias enormes, y llegado un punto el sistema se queda sin memoria disponible. Ahí es donde entra en juego el OOM Killer del kernel de Linux, que mata los procesos que más memoria consumen (o los que el algoritmo considera más sacrificables) para evitar que el sistema entero se hunda.

Este comportamiento se suele ver muy bien en gráficos de monitorización: la memoria RSS de un proceso sube poco a poco durante días hasta que, de repente, cae en picado en el momento en que el OOM Killer lo liquida y el servicio se reinicia. Si nadie analiza esos eventos, la fuga seguirá ahí y el ciclo se repetirá una y otra vez.

Por eso es clave entender que las fugas de memoria no son solo un problema de código, sino también de operación y observabilidad: hay que saber detectarlas en producción, correlacionarlas con eventos del sistema y disponer de herramientas para analizarlas tanto en procesos que ya están corriendo como en entornos de pruebas.

Causas típicas y síntomas claros de un memory leak

Desde el punto de vista del desarrollo, las causas más habituales de fugas de memoria suelen ser errores de programación y malas prácticas de gestión de recursos. Entre los motivos más comunes están: olvidarse de liberar memoria, mantener estructuras en memoria que nunca se podan, no cerrar descriptores que llevan asociado un buffer, o usar librerías de terceros con bugs internos.

En aplicaciones de larga ejecución, como demonios, servicios web o procesos batch que nunca se reinician, los problemas se agravan porque cualquier pequeño leak se acumula con el tiempo. Incluso un fallo raro que solo se produce en un edge case concreto puede terminar devorando la RAM si el proceso se mantiene vivo meses.

Los síntomas que deberían ponerte en alerta son bastante reconocibles si sabes qué mirar. El más evidente es ver cómo la memoria de un proceso (RES/RSS) crece de forma continua aunque la carga de trabajo se mantenga estable. Es como ver el indicador de combustible bajar en un coche que está aparcado.

Otro efecto típico es la degradación progresiva del rendimiento. El sistema empieza a usar swap, las latencias se disparan, consultas o peticiones que antes eran rápidas se vuelven eternas y el resto de procesos del host también se resienten, aunque ellos no sean los culpables directos.

Finalmente aparece el síntoma más dramático: caídas inesperadas de procesos o del sistema completo. El kernel, sin memoria que asignar, activa el Out-Of-Memory Killer y mata procesos. Si el que cae es un microservicio aislado, duele, pero si el que muere es, por ejemplo, la base de datos o el gestor de colas, el impacto puede ser muy serio.

Una forma muy eficaz de identificar estos casos en producción es combinar gráficos de memoria con la recopilación de eventos del sistema, incluyendo los generados por el OOM Killer. Si ves en tus dashboards un patrón de consumo de memoria que sube lentamente, se desploma de golpe, y justo en ese instante tienes uno o varios eventos del OOM Killer, casi seguro que estás ante un memory leak en ese proceso.

Monitorización básica con top y htop

Para una primera aproximación, no hace falta complicarse mucho la vida: herramientas como top y, sobre todo, htop son perfectas para ver en tiempo real qué procesos se están comiendo tu memoria. Son como un panel de control rápido donde localizar a los sospechosos.

  Cómo comprobar la suma de verificación (checksum) en Windows, Linux y macOS

En la mayoría de distribuciones puedes instalar htop fácilmente con el gestor de paquetes. En sistemas basados en Debian bastaría con algo así:

sudo apt install htop

Una vez instalado, al ejecutar htop verás una vista interactiva de procesos con colores, barras de CPU y memoria y diferentes columnas. Las columnas clave para detectar fugas son la memoria residente y la memoria virtual del proceso:

RES / RSS (Resident Set Size): memoria física que el proceso tiene actualmente en RAM.
VIRT (Virtual Memory): memoria virtual total que el proceso tiene asignada (incluye memoria mapeada y potencialmente intercambiada).
%MEM: porcentaje de RAM física que dicho proceso está consumiendo sobre el total del sistema.

Si ordenas por RES o %MEM y dejas htop abierto un buen rato, puedes ir viendo cómo evolucionan los procesos. Si uno de ellos no deja de subir poco a poco en estas columnas sin volver jamás a bajar, pinta a fuga de memoria o, como mínimo, a un uso poco sano de la misma.

top, aunque sea más sobrio, también te permite ver estos valores y monitorizar durante un periodo de tiempo, pero htop facilita mucho más la observación prolongada y el filtrado de procesos concretos que te interesen.

Profundizando con el sistema de ficheros /proc

Para pasar de una visión superficial a un análisis más fino, Linux expone información detallada de cada proceso en el pseudo filesystem /proc. Cada PID tiene su propio directorio dentro de /proc, y ahí puedes consultar todo tipo de métricas, entre ellas las relacionadas con la memoria.

Un punto de entrada clásico es el fichero /proc//status, donde encontrarás campos como VmRSS, VmSize o VmData. Puedes echarles un vistazo con un simple:

cat /proc/<pid>/status

En esa salida, los campos más interesantes de cara a detectar fugas de memoria son:

VmRSS: memoria residente (en KB) que el proceso tiene en RAM en ese momento.
VmSize: memoria virtual total asociada al proceso (incluye todo lo que haya mapeado).
VmData: memoria de segmento de datos, donde suelen residir estructuras dinámicas y heap, una zona muy propensa a memory leaks.

La idea práctica es ir consultando estos valores a intervalos regulares (ya sea a mano o mediante scripts) y observar si siguen una tendencia creciente constante. Si ves que VmRSS y, sobre todo, VmData crecen sin reducción en periodos de poca carga, es una señal bastante sólida de que la aplicación está perdiendo memoria.

Además de status, en /proc/ tienes otros ficheros interesantes para analizar el mapa de memoria, como maps o smaps, aunque son más verbosos y suelen utilizarse junto a otras herramientas como pmap para hacer más legible la información.

Análisis del mapa de memoria de un proceso con pmap

La utilidad pmap es un comando muy útil para obtener una visión organizada del mapa de memoria de un proceso concreto. En esencia, lo que hace es mostrar qué rangos de direcciones tiene mapeados el proceso, qué tamaño tiene cada uno, con qué permisos cuenta y a qué fichero, biblioteca o tipo de memoria corresponden.

Para usarlo, basta con lanzar:

pmap

En la salida verás líneas con la dirección inicial, el tamaño, los permisos (lectura, escritura, ejecución), y la procedencia (por ejemplo, el ejecutable principal, una librería compartida como libc, zonas anónimas, la pila, etc.). Las regiones de memoria anónima y la zona de heap son las que suelen dar pistas cuando hay memory leaks.

Una forma práctica de seguimiento es repetir pmap cada cierto tiempo y fijarse en si ciertos segmentos (especialmente los anónimos relacionados con el heap) no paran de crecer. También puedes filtrar o resumir la salida, por ejemplo:

pmap <pid> | grep total

Esto te dará un resumen de la memoria total mapeada por el proceso. Si ese número sube, sube y sube con el paso de las horas y no se estabiliza ni baja cuando debería, la sospecha vuelve a recaer en una fuga de memoria o en una gestión poco eficiente de los buffers internos.

smem: diferenciando memoria compartida y uso real de cada proceso

Herramientas como top, htop o pmap cuentan toda la memoria que un proceso tiene referenciada, pero no hacen una separación clara entre lo que es memoria realmente exclusiva de ese proceso y lo que es memoria compartida con otros (por ejemplo, bibliotecas compartidas). Ahí entra en juego smem, una utilidad específica para aportar una visión más precisa.

La gran ventaja de smem es que calcula métricas como USS (Unique Set Size), PSS (Proportional Set Size) y RSS, que permiten entender mejor el uso real de memoria por proceso: cuánta memoria es solo suya y cuánta está repartida con otros procesos que cargan las mismas librerías o comparten páginas.

Algunas de las métricas más relevantes que vas a ver en la salida de smem son:

USS (Unique Set Size): memoria que solo utiliza ese proceso; si desaparece el proceso, esa porción de memoria se libera por completo.
PSS (Proportional Set Size): reparte la memoria compartida entre todos los procesos que la están usando, ofreciendo una visión proporcional bastante justa de la huella real.
RSS (Resident Set Size): la memoria residente, como en otras herramientas, pero presentada junto con las anteriores para poder comparar.

Al ejecutar algo como smem -k obtendrás una tabla con PID, usuario, comando y estas columnas de uso de memoria. Lo interesante, de cara a los memory leaks, es fijarse sobre todo en la USS, ya que refleja la memoria propia de la aplicación, que es donde suelen aparecer las fugas más graves.

Si dejas corriendo smem periódicamente (o lo integras en scripts de monitorización) y ves que la USS de un determinado proceso no deja de aumentar con el tiempo, incluso cuando su carga no crece, el comportamiento es muy indicativo de una fuga de memoria en la parte de memoria única de ese proceso.

  Cómo actualizar Ubuntu 24.04 a Ubuntu 26.04 LTS paso a paso

memleax: detección automática de fugas en procesos ya en ejecución

Cuando ya tienes localizado un proceso que parece perder memoria y quieres ir un paso más allá sin reiniciarlo, una herramienta muy interesante es memleax. Su gran baza es que permite detectar fugas de memoria en tiempo real sobre un proceso que ya está corriendo, sin necesidad de recompilarlo ni relanzarlo con un comando especial.

memleax se distribuye principalmente en forma de paquetes .rpm y .deb, y está disponible en algunos repositorios como los de Arch Linux y FreeBSD. En sistemas basados en Debian, una forma habitual de instalarlo es descargar el paquete desde su repositorio oficial en GitHub y luego usar dpkg para instalarlo, resolviendo las dependencias con tu gestor de paquetes.

Una vez instalado, puedes adjuntar memleax a un proceso con:

sudo memleax -p

A partir de ese momento, memleax intercepta las llamadas de asignación de memoria (como malloc) y va registrando las direcciones y tamaños que el proceso reserva. Cuando detecta que una asignación no se ha liberado correctamente, lo marca explícitamente como fuga de memoria, indicando el tamaño del bloque y la dirección responsable.

La salida típica muestra líneas del estilo malloc(128) = 0x... seguidas, cuando hay un problema, de mensajes que señalan algo como memory leak detected para un bloque concreto. Esta información es muy útil porque te indica que, mientras el proceso sigue vivo y trabajando, hay bloques que se están quedando huérfanos.

memleax resulta especialmente atractivo para entornos de producción o preproducción donde no puedes permitirte reiniciar el servicio con un depurador o con Valgrind desde cero, pero necesitas entender qué está pasando dentro en cuanto a gestión de memoria dinámica.

Uso de gdb para inspeccionar en profundidad la memoria de un proceso

Si necesitas un nivel de detalle todavía mayor y te puedes permitir una depuración más intrusiva, el GNU Debugger (gdb) es tu aliado. Se trata de una herramienta potentísima que permite adjuntarse a un proceso ya en marcha, inspeccionar variables, pilas de llamadas y, por supuesto, el estado del heap.

Para comenzar, instalas gdb desde los repositorios de tu distribución (por ejemplo, con sudo apt install gdb en Debian/Ubuntu) y luego lo adjuntas a un proceso con:

sudo gdb -p

Una vez dentro de la sesión de gdb, puedes utilizar distintos comandos relacionados con el heap. En algunos entornos está disponible directamente el comando heap (o extensiones que lo proporcionan) para listar los bloques de memoria dinámica actualmente en uso, con sus direcciones y tamaños. La salida muestra algo como una lista de chunk de memoria, cada uno con una dirección y un tamaño, marcados como in use.

Además, desde gdb puedes invocar funciones de la libc como malloc_stats() mediante:

(gdb) call malloc_stats()

Este tipo de llamada te da un resumen del estado del asignador de memoria: cuánto se ha reservado, cómo está repartido el heap, etc. Es una forma relativamente rápida de ver si la memoria asignada por el proceso está creciendo sin control.

Otro enfoque potente es colocar breakpoints en funciones como malloc o free para observar en tiempo real cómo se comporta el código: cuántas veces reserva, en qué puntos se libera, qué rutas de código hacen muchas allocs pero pocas frees… Aunque esto requiere más pericia en depuración, es una vía directa para localizar el origen exacto de las fugas.

Valgrind: el clásico perfilador de memoria en Linux

Si hablamos de detección de fugas de memoria en entornos Linux, es imposible no mencionar Valgrind. Más que una sola herramienta, Valgrind es un marco de depuración y perfilado que incluye varios módulos, siendo el más conocido y usado Memcheck, diseñado específicamente para cazar problemas de memoria.

Memcheck funciona ejecutando tu programa dentro de una especie de máquina virtual que intercepta y monitoriza todas las operaciones relacionadas con memoria: asignaciones, liberaciones, accesos a direcciones, etc. Además, sustituye el asignador de memoria estándar de C por uno propio, que introduce protecciones adicionales alrededor de los bloques reservados para detectar accesos fuera de rango.

Entre los tipos de errores que puede detectar Memcheck destacan: uso de memoria sin inicializar, lecturas/escrituras después de liberar memoria, accesos ilegales a zonas de memoria no pertenecientes al programa y, por supuesto, fugas de memoria de distintos tipos (bloques definitivamente perdidos, posiblemente perdidos, todavía alcanzables, etc.).

Su uso básico es relativamente sencillo. Compilas tu programa con símbolos de depuración (por ejemplo, con -g o -gstabs) y luego lo ejecutas bajo Valgrind con algo como:

valgrind --tool=memcheck --leak-check=full -v ./tu_programa

En un programa que gestione bien la memoria, la salida de Memcheck mostrará un resumen de errores con cero incidencias. Es decir, ni lecturas ilegales, ni escrituras fuera de rango, ni bytes de heap en uso al salir del programa. Ese suele ser el primer paso: verificar un caso “sano” para ver cómo luce una ejecución limpia.

Si introduces a propósito un malloc sin su correspondiente free, o cualquier otro patrón de fuga, y vuelves a ejecutar el binario con Valgrind, verás en la salida un HEAP SUMMARY indicando cuánta memoria sigue “en uso” al finalizar la aplicación. Bajo el apartado LEAK SUMMARY aparecerán líneas como “definitely lost” con los bytes y bloques que se han quedado sin liberar.

Además, Memcheck te dirá exactamente dónde se produjo la asignación que originó la fuga: verás una traza de llamadas con funciones, ficheros fuente y números de línea. Por ejemplo, puede indicar que un malloc en cierta línea de tu fichero .c ha originado un bloque que nunca ha sido liberado, arrojando luz de forma inmediata sobre el foco del problema.

Valgrind también es muy eficaz detectando otros errores clásicos: por ejemplo, escrituras ilegales en memoria (como escribir en la dirección 0 o fuera de los límites de un array), uso de variables sin inicializar (mostrando mensajes del tipo “Conditional jump or move depends on uninitialised value(s)”) o liberaciones incorrectas (como hacer free sobre un puntero que no proviene de malloc, o liberar dos veces el mismo bloque).

  Escalada a root en Ubuntu: técnicas, riesgos y seguridad

En todos estos casos, el informe de Memcheck detalla dónde se produjo el acceso erróneo o el free ilegal, qué función lo originó, y en qué parte del código se creó esa variable o se reservó esa memoria, lo que lo convierte en una herramienta prácticamente imprescindible para depurar a fondo la gestión de memoria en C y C++.

Otros perfiladores de memoria: gperftools, Massif y compañía

Aunque Valgrind-Memcheck suele ser la primera opción, hay otros generadores de perfiles y herramientas que complementan muy bien el análisis de fugas y patrones de uso de memoria. Uno de ellos es gperftools (antiguo Google Performance Tools), que incluye un heap profiler capaz de registrar el uso de memoria a lo largo del tiempo y generar informes visuales (por ejemplo, con pprof) que muestran qué partes del código están reservando más memoria.

Otra herramienta de la familia Valgrind es Massif, enfocada específicamente en perfilado de memoria de heap. En lugar de centrarse solo en errores, Massif mide el tamaño del heap a lo largo de la ejecución y genera datos que luego puedes visualizar para entender en qué fases de tu programa crece más la memoria y qué estructuras o funciones son responsables.

En general, estos perfiladores funcionan interceptando operaciones de memoria de forma similar a Valgrind o mediante librerías de instrumentación, y registran estadísticas detalladas de las asignaciones y liberaciones. Al final, te proporcionan informes que incluyen número de asignaciones, tamaño total reservado, ubicaciones concretas en el código donde se realizan las reservas más pesadas y, por supuesto, bloques que nunca se liberan.

El flujo de trabajo típico consiste en ejecutar el programa bajo el perfilador (en un entorno lo más parecido posible a producción pero controlado), reproducir la carga o el caso de uso que sospechas que provoca la fuga y, posteriormente, analizar los informes generados con herramientas gráficas o de línea de comandos. De este modo puedes ver de un vistazo qué caminos de código son los que hacen crecer la memoria sin control.

Estrategias proactivas: pruebas de carga, límites y buenas prácticas

Todo lo anterior sirve para detectar problemas una vez que ya están ahí, pero la estrategia ideal es evitar que las fugas lleguen a producción o, al menos, cazarlas cuanto antes. Para eso conviene combinar varias tácticas a nivel de testing, configuración del sistema y calidad de código.

En primer lugar, tiene mucho sentido realizar pruebas de carga y estrés en entornos de preproducción que sean lo más realistas posible. Herramientas como Apache JMeter, Locust, stress y similares permiten simular usuarios concurrentes, peticiones intensivas o escenarios prolongados. Durante estas pruebas debes monitorizar cuidadosamente las métricas de memoria (RSS, heap, etc.) para ver si hay crecimientos lentos pero continuos.

En paralelo, conviene monitorizar no solo las métricas en bruto, sino también eventos del sistema como los del OOM Killer. Plataformas de observabilidad y monitorización de logs pueden agrupar esta información y avisarte cuando, por ejemplo, en un host concreto se acumulan eventos de OOM, lo que suele apuntar a procesos mal comportados o a falta de recursos.

A nivel de configuración del sistema, Linux ofrece mecanismos para limitar el impacto de un proceso que “se va de madre”. Por ejemplo, con ulimit puedes imponer límites de memoria a procesos lanzados por un determinado usuario, poniendo un techo al tamaño de su memoria virtual. Algo como ulimit -v <kilobytes> evitará que un único servicio consuma toda la RAM del host.

Para escenarios más avanzados, los cgroups (control groups) permiten aislar y restringir el consumo de recursos (CPU, memoria, I/O…) de grupos de procesos. Puedes crear un cgroup con un límite de memoria concreto y asignar a él un servicio, de forma que, si hay una fuga de memoria, el daño quede encapsulado en ese grupo y no afecte al sistema entero.

Por último, en el plano del desarrollo, la mejor defensa sigue siendo escribir código que gestione bien la memoria desde el principio. En C/C++ implica ser riguroso con cada malloc/new y su correspondiente free/delete, usar patrones RAII, smart pointers (como std::shared_ptr, std::unique_ptr) y evitar mantener referencias innecesarias. También puedes consultar un tutorial de Rust para alternativas con seguridad de memoria. En lenguajes con garbage collector (Java, C#, Go, Python, JavaScript, etc.) también es fácil provocar pseudo-fugas si mantienes referencias vivas a objetos que ya no necesitas.

Herramientas de análisis estático como cppcheck, SonarQube y similares ayudan a detectar patrones de código sospechosos durante el desarrollo. Si a esto le sumas revisiones de código cuidadosas, tests de unidad que verifiquen comportamiento bajo carga y ejecuciones periódicas de Valgrind u otros perfiladores en entornos de CI, las probabilidades de que una fuga seria llegue a producción se reducen muchísimo.

En definitiva, mantener a raya las fugas de memoria en Linux pasa por combinar monitorización continua, herramientas de diagnóstico potentes y buenas prácticas de programación. Con top, htop, /proc, pmap y smem puedes localizar procesos sospechosos; con memleax y gdb inspeccionas en caliente; con Valgrind, gperftools o Massif depuras y perfilas a fondo; y con pruebas de carga, límites de sistema y código bien escrito te adelantas a que el problema explote en el peor momento posible.

tutorial de apache jmeter
Related article:
Tutorial completo de Apache JMeter para pruebas de rendimiento