En Linux es normal ver la memoria "casi llena": el kernel usa la RAM libre como caché de disco y la devuelve en cuanto una aplicación la necesita. El problema real empieza cuando la memoria disponible se agota, el sistema empieza a usar swap de forma continua o el OOM killer mata procesos. En esta guía aprenderás a distinguir ambas situaciones en Ubuntu 24.04, a localizar qué proceso o componente consume la memoria, a detectar fugas y a limitar el consumo de un servicio.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. En Debian 12 los comandos son los mismos.
- Un usuario no root con privilegios
sudo. - Los paquetes
sysstatysmem, que instalarás en el primer paso.
Paso 1: Instalar las herramientas
free, ps y vmstat vienen con el sistema. pidstat (del paquete sysstat) permite seguir la memoria de un proceso a lo largo del tiempo, y smem calcula la memoria real de cada proceso teniendo en cuenta la memoria compartida:
sudo apt update
sudo apt install sysstat smem
Paso 2: Leer correctamente la salida de free
Empieza siempre por free con unidades legibles:
free -h
total used free shared buff/cache available
Mem: 3.8Gi 2.9Gi 151Mi 42Mi 1.0Gi 702Mi
Swap: 2.0Gi 1.4Gi 614Mi
Cada columna significa lo siguiente:
| Columna | Significado |
|---|---|
used | Memoria usada por procesos y el kernel, sin contar caché |
free | Memoria que no se usa para nada. Suele ser baja y no es preocupante |
shared | Memoria compartida y ficheros en tmpfs (como /dev/shm) |
buff/cache | Caché de disco. El kernel la libera cuando hace falta |
available | Estimación de la memoria disponible para nuevas aplicaciones sin usar swap |
La columna importante es available. En el ejemplo quedan 702 MiB de 3,8 GiB y se usan 1,4 GiB de swap: el servidor sí tiene presión de memoria. Si free fuera baja pero available alta, no habría ningún problema.
Paso 3: Comprobar si hay presión de memoria
Que haya swap usada no es grave por sí mismo: puede ser memoria que se intercambió hace días y no se ha vuelto a tocar. Lo grave es el intercambio continuo. Compruébalo con vmstat:
vmstat 2 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 3 1468212 148620 10204 982316 840 1260 2210 1480 1905 3120 18 12 38 31 1
1 4 1470100 139880 10204 979004 912 1310 2344 1532 1988 3244 16 14 36 33 1
Si las columnas si (entrada desde swap) y so (salida a swap) no son cero de forma sostenida, el sistema está constantemente moviendo páginas al disco, lo que dispara el wa (espera de E/S) y hace que todo vaya lento.
El kernel también expone la presión de memoria (PSI), que indica qué porcentaje de tiempo han estado las tareas bloqueadas esperando memoria:
cat /proc/pressure/memory
some avg10=12.40 avg60=9.85 avg300=4.12 total=184502331
full avg10=5.10 avg60=3.97 avg300=1.60 total=80213442
Valores de avg10 cercanos a cero son lo normal. Si full supera unos pocos puntos de forma sostenida, hay momentos en que ninguna tarea puede avanzar por falta de memoria.
Paso 4: Encontrar los procesos que más memoria usan
Con ps
ps ordena los procesos por memoria residente (RSS, la RAM física que ocupan, en KiB):
ps -eo pid,user,rss,%mem,etime,cmd --sort=-rss | head -10
PID USER RSS %MEM ELAPSED CMD
1204 mysql 1210448 30.4 6-03:12:40 /usr/sbin/mysqld
9812 www-data 184320 4.6 02:11 php-fpm: pool www
9813 www-data 182104 4.5 02:10 php-fpm: pool www
Con smem
El RSS cuenta varias veces la memoria compartida: si diez procesos de PHP-FPM comparten 100 MiB de librerías, cada uno la suma en su RSS. smem calcula el PSS (Proportional Set Size), que reparte la memoria compartida entre los procesos que la usan, así que la suma se acerca al consumo real:
sudo smem -rs pss -k | head -10
Para ver el total por usuario (útil para ver cuánto ocupan en conjunto todos los workers de www-data):
sudo smem -u -k
Por servicio
Cada servicio de systemd se ejecuta en su propio cgroup, que suma la memoria de todos sus procesos (incluida la caché de sus ficheros). Para verlo ordenado por memoria:
systemd-cgtop -m
Pulsa q para salir. Es la forma más rápida de saber qué servicio, y no qué proceso suelto, consume más.
Paso 5: Revisar la memoria que no pertenece a ningún proceso
A veces la suma de los procesos no explica la memoria usada. En ese caso, consulta el desglose del kernel:
grep -E '^(MemTotal|MemAvailable|Cached|Shmem|AnonPages|Slab|SReclaimable|SUnreclaim|HugePages_Total|Hugepagesize):' /proc/meminfo
MemTotal: 3995232 kB
MemAvailable: 718900 kB
Cached: 901224 kB
AnonPages: 2410388 kB
Shmem: 43104 kB
Slab: 412880 kB
SReclaimable: 98420 kB
SUnreclaim: 314460 kB
HugePages_Total: 0
Hugepagesize: 2048 kB
Qué revisar en cada campo:
AnonPages: memoria de las aplicaciones (heap, pilas). Debe cuadrar aproximadamente con la suma de procesos.Shmem: memoria compartida ytmpfs. Si es alta, revisa qué ocupa espacio entmpfscondf -h -t tmpfs; los ficheros en/dev/shmo/runviven en RAM.SUnreclaim: memoria del kernel que no se puede liberar. Si crece sin parar, sospecha de un módulo o driver con fugas;sudo slabtop -o | head -20muestra qué caché del kernel crece.HugePages_Total: las huge pages reservadas se descuentan de la memoria disponible aunque no se usen.
Paso 6: Detectar eventos del OOM killer
Cuando la memoria se agota del todo, el kernel elige un proceso y lo mata. Si un servicio "se cae solo", busca en el log del kernel:
sudo journalctl -k | grep -iE 'out of memory|oom-kill|killed process'
Sep 25 03:14:07 web01 kernel: Out of memory: Killed process 1204 (mysqld) total-vm:2841212kB, anon-rss:1810244kB, file-rss:0kB, shmem-rss:0kB, UID:112 pgtables:4160kB oom_score_adj:0
La línea indica qué proceso murió y cuánta memoria anónima (anon-rss) tenía en ese momento. Recuerda que el proceso matado no siempre es el culpable: el kernel mata al que más memoria usa, que suele ser la base de datos, aunque la fuga esté en otro proceso.
Para ver qué proceso sería el siguiente candidato, consulta su puntuación (a mayor valor, más probable):
cat /proc/PID/oom_score
Si quieres proteger un servicio crítico para que el OOM killer lo elija en último lugar, añade OOMScoreAdjust a su unidad. Por ejemplo, para MySQL:
sudo systemctl edit mysql
Añade en la zona editable:
[Service]
OOMScoreAdjust=-500
Reinicia el servicio para aplicarlo:
sudo systemctl restart mysql
Paso 7: Detectar fugas de memoria
Una fuga se reconoce porque la memoria de un proceso crece de forma continua y nunca baja, aunque la carga sea estable. pidstat -r imprime el RSS de un proceso en intervalos; este ejemplo toma una muestra por minuto:
pidstat -r -p PID 60
11:00:00 UID PID minflt/s majflt/s VSZ RSS %MEM Command
11:01:00 33 9812 12.40 0.00 412220 184320 4.61 php-fpm8.3
11:02:00 33 9812 10.95 0.00 418364 190512 4.77 php-fpm8.3
11:03:00 33 9812 11.20 0.00 424508 196704 4.92 php-fpm8.3
Déjalo en marcha un buen rato (pulsa Ctrl+C para parar). Un RSS que sube unos MiB por minuto sin estabilizarse es una fuga. La columna majflt/s (fallos de página mayores) indica que el proceso está leyendo páginas desde el disco o la swap.
Mitigaciones habituales mientras se corrige el código:
- PHP-FPM: fija
pm.max_requests = 500en el pool para que cada worker se recicle tras 500 peticiones. - Aplicaciones con systemd: limita la memoria como se muestra en el paso 8 para que la fuga no afecte al resto del sistema.
Paso 8: Limitar la memoria de un servicio
systemd permite fijar límites por servicio usando cgroups v2. MemoryHigh frena al servicio (le obliga a liberar memoria) cuando supera el valor, y MemoryMax es el límite absoluto, a partir del cual el OOM killer actúa solo dentro de ese servicio. Por ejemplo, para myapp.service:
sudo systemctl set-property myapp.service MemoryHigh=800M MemoryMax=1G
Comprueba que se ha aplicado y cuánta memoria usa el servicio:
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p MemoryCurrent
MemoryCurrent=624902144
MemoryHigh=838860800
MemoryMax=1073741824
Para contenedores Docker, el equivalente es la opción --memory (por ejemplo docker run --memory=1g ...) o mem_limit en Docker Compose.
Revisa también la configuración de memoria de tus aplicaciones, porque muchas reservan memoria de forma fija:
- MySQL:
innodb_buffer_pool_sizeno debe superar en torno al 50-70 % de la RAM si comparte servidor con otras aplicaciones. - PHP-FPM:
pm.max_childrenmultiplicado por la memoria de cada worker (el PSS que viste consmem) debe caber en la RAM disponible. - Java: fija el heap con
-Xmx.
Paso 9: Añadir swap o ajustar swappiness
Una pequeña swap da margen ante picos puntuales y evita que el OOM killer actúe de inmediato. No sustituye a la RAM: si el sistema intercambia continuamente, necesitas más memoria o reducir el consumo. Comprueba primero si ya tienes swap:
swapon --show
Si no aparece nada, crea un fichero de swap de 2 GB:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
Para que se active en cada arranque, añádelo a /etc/fstab:
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Verifícalo:
swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2
El parámetro vm.swappiness (60 por defecto) controla cuánto prefiere el kernel usar swap frente a descartar caché. En servidores con bases de datos suele bajarse a 10 para que la memoria de las aplicaciones se mantenga en RAM:
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system
sysctl vm.swappiness
vm.swappiness = 10
Solución de problemas
freemuestra poca memoria libre peroavailablees alta: es el comportamiento normal de Linux. No vacíes la caché condrop_caches: solo conseguirás que el disco trabaje más mientras la caché se vuelve a llenar.- La memoria usada no cuadra con la suma de procesos: revisa
Shmem,SUnreclaimyHugePagesen/proc/meminfo(paso 5). También puede ser la caché ARC si usas ZFS. fallocatefalla al crear la swap conOperation not supported: el sistema de ficheros no lo admite. Usasudo dd if=/dev/zero of=/swapfile bs=1M count=2048en su lugar.- Un servicio con
MemoryMaxse reinicia continuamente: el límite es demasiado bajo para su carga normal. ConsultaMemoryCurrentdurante el funcionamiento normal y sube el límite con margen.
Conclusión
Ahora sabes distinguir la caché normal de una presión de memoria real, localizar qué proceso, servicio o componente del kernel consume la RAM, reconocer los eventos del OOM killer y detectar fugas. Como siguientes pasos, aplica límites MemoryMax a los servicios propensos a crecer, activa la recogida de sysstat para tener histórico con sar -r, y si la memoria disponible se queda corta de forma habitual, valora ampliar tu VPS a un plan con más RAM.
