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 sysstat y smem, 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:

ColumnaSignificado
usedMemoria usada por procesos y el kernel, sin contar caché
freeMemoria que no se usa para nada. Suele ser baja y no es preocupante
sharedMemoria compartida y ficheros en tmpfs (como /dev/shm)
buff/cacheCaché de disco. El kernel la libera cuando hace falta
availableEstimació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 y tmpfs. Si es alta, revisa qué ocupa espacio en tmpfs con df -h -t tmpfs; los ficheros en /dev/shm o /run viven 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 -20 muestra 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 = 500 en 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_size no debe superar en torno al 50-70 % de la RAM si comparte servidor con otras aplicaciones.
  • PHP-FPM: pm.max_children multiplicado por la memoria de cada worker (el PSS que viste con smem) 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

  • free muestra poca memoria libre pero available es alta: es el comportamiento normal de Linux. No vacíes la caché con drop_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, SUnreclaim y HugePages en /proc/meminfo (paso 5). También puede ser la caché ARC si usas ZFS.
  • fallocate falla al crear la swap con Operation not supported: el sistema de ficheros no lo admite. Usa sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 en su lugar.
  • Un servicio con MemoryMax se reinicia continuamente: el límite es demasiado bajo para su carga normal. Consulta MemoryCurrent durante 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.