Servidor Minecraft: solucionar "java.lang.OutOfMemoryError: Java heap space"
El mensaje java.lang.OutOfMemoryError: Java heap space no significa automáticamente que falte RAM. Cómo dimensionar Xmx y Xms correctamente, encontrar fugas de memoria y usar el swap con criterio.
El servidor lleva tres horas funcionando, de pronto se congela, la tasa de ticks cae a 2 y en la consola aparece esto:
[Server thread/ERROR]: Encountered an unexpected exception
java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3537)
at it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap.rehash(...)
El primer reflejo es casi siempre el mismo: asignar más RAM. En aproximadamente la mitad de los casos esa es justo la reacción equivocada, y en una parte de ellos empeora la situación de forma medible. Este artículo explica qué significa realmente el mensaje, cómo dimensionar -Xmx y -Xms con criterio, cómo distinguir una fuga de memoria de una falta real de memoria y en qué notas que la solución ha aguantado.
Qué significa exactamente el mensaje, y qué no
El heap de Java es la zona donde la JVM guarda los objetos: chunks cargados, entidades, inventarios, datos de plugins. Su límite superior lo fijas con -Xmx. Un OutOfMemoryError: Java heap space significa que la JVM quiso crear un objeto, el heap estaba lleno y la garbage collection no consiguió liberar espacio suficiente. Eso no dice nada sobre la memoria libre del sistema operativo. Un servidor con 64 GB de RAM lanza ese mismo mensaje con idéntica fiabilidad si tiene -Xmx2G y el mundo necesita 4 GB.
Hay que distinguir esto con precisión de otros dos fallos que se confunden con frecuencia:
- El proceso desaparece sin stacktrace, en el log solo pone
Killedo en el journalMain process exited, code=killed, status=9/KILL. Ahí actuó el kernel, no la JVM. El OOM killer de Linux intervino porque se había agotado la memoria completa del sistema. Puedes comprobarlo condmesg | grep -i "out of memory", donde aparecerá entonces una línea comoOut of memory: Killed process 1337 (java). - Java ni siquiera arranca y avisa con
Error occurred during initialization of VM / Could not reserve enough space for object heap. En ese caso-Xmxes mayor de lo que el sistema puede llegar a ofrecer.
Esta distinción es el paso más importante. En la primera variante falta heap, en la segunda y la tercera sobra. Quien confunde los casos acaba girando el tornillo equivocado.
Primero medir, después asignar
Antes de cambiar ningún valor necesitas dos cifras: la memoria realmente disponible y el consumo actual.
free -h
cat /proc/meminfo | grep -E 'MemTotal|MemAvailable|SwapTotal'
Lo decisivo es MemAvailable, no free. Linux aprovecha la RAM sin usar como caché de archivos, por eso "free" es casi siempre un valor pequeño y casi siempre irrelevante.
El consumo real del servidor lo ves así:
ps -o pid,rss,cmd -C java
El valor RSS viene en kilobytes y es la memoria que el proceso ocupa en la RAM. Es siempre mayor que -Xmx, y ese es justo el punto en el que se detienen la mayoría de las guías.
Por qué asignar "todo lo posible" sale mal con toda seguridad
Además del heap, la JVM necesita toda una serie de zonas de memoria adicionales que -Xmx no cubre en absoluto:
- Metaspace: las clases cargadas. Con un modpack de 300 mods son fácilmente entre 300 y 600 MB.
- Thread stacks: cada hilo recibe alrededor de 1 MB. Workers de chunks, hilos de Netty, schedulers de plugins, todo eso suma entre 100 y 300 MB.
- Direct buffers: Netty gestiona todo el tráfico de red con memoria situada fuera del heap. Con muchos jugadores simultáneos, varios cientos de megabytes son lo normal.
- Code cache y estructuras del GC: el compilador JIT y la propia contabilidad interna de G1 cuestan a grandes rasgos entre un 5 y un 10 por ciento del heap.
Como regla práctica fiable: cuenta con Xmx más 1 a 1,5 GB para la JVM, más al menos 512 MB para el sistema operativo. Con un modpack de muchos mods, mejor Xmx más 2 GB.
En un servidor con 8 GB de RAM eso significa -Xmx6G y no -Xmx8G. Quien asigna 8 ya no recibe un error de heap, sino algo peor: un proceso al que el kernel mata sin previo aviso, justo mientras se guarda el mundo. El error de heap es un fallo limpio y documentado. El OOM kill puede dejar atrás archivos de región corruptos.
Hay un segundo motivo en contra de asignar el máximo: un heap demasiado grande vuelve más lenta la garbage collection. G1 tiene que recorrer más memoria, las pausas de un mixed GC se alargan y lo que era un tirón ocasional se convierte en un freeze perceptible. Por encima de unos 12 GB, en Minecraft la relación suele volverse negativa. Quien necesite más debería repartir el mundo en lugar de agrandar el heap.
Reglas prácticas según número de jugadores y modpack
Estos valores son puntos de partida, no leyes naturales. Parten de un mundo de tamaño normal y de un pregenerado activo.
| Tipo de servidor | Jugadores | Xmx | RAM del sistema |
|---|---|---|---|
| Vanilla o Paper, sin plugins | hasta 10 | 2G | 4 GB |
| Paper con 15 a 30 plugins | 10 a 30 | 4G | 8 GB |
| Paper, suite grande de plugins, base de datos | 30 a 80 | 6G a 8G | 12 a 16 GB |
| Modpack ligero, hasta 120 mods | hasta 10 | 6G | 8 GB |
| Modpack medio, 150 a 250 mods | hasta 20 | 8G a 10G | 16 GB |
| Modpack pesado, a partir de 300 mods | hasta 20 | 10G a 12G | 16 a 24 GB |
| Proxy (Velocity, BungeeCord) | cualquiera | 512M a 1G | 2 GB |
Dos apuntes al respecto. Primero, en los modpacks la necesidad de heap escala casi en exclusiva con el número de mods y el tamaño del mundo, apenas con el número de jugadores. Segundo, view-distance en server.properties es la palanca más eficaz que existe: bajar de 10 a 8 ahorra a menudo más memoria que 2 GB extra de heap, porque el número de chunks cargados crece de forma cuadrática con la distancia de visión. Poner simulation-distance en 6 actúa además sobre la carga de CPU.
Xms igual a Xmx: el calentamiento
-Xms determina con cuánto heap arranca la JVM. Si ahí hay un valor menor que en -Xmx, el heap va creciendo poco a poco durante el funcionamiento. Cada ampliación supone una recolección completa de la garbage collection y nuevos page faults en el sistema operativo, y como G1 también deja que el heap vuelva a encogerse, el juego se repite. De ahí vienen justamente los famosos tirones cada pocos minutos durante las primeras horas tras el arranque.
Pon -Xms siempre igual a -Xmx. Si lo completas con -XX:+AlwaysPreTouch, la JVM toca una vez cada página de memoria del heap durante el arranque. El inicio tarda así entre 2 y 15 segundos más según el tamaño del heap, pero a cambio ya no se producen page faults durante la partida. El efecto secundario agradable: si -Xmx está elegido demasiado alto, por lo general eso se nota ya en el arranque y no tres horas más tarde en plena partida.
Pero no te fíes para ello de una prueba rápida con -version. Medido en la práctica: java -Xms4G -Xmx4G -XX:+AlwaysPreTouch -version se ejecutó sin problemas en un entorno limitado a 2 GB, porque la llamada termina antes de que el heap esté realmente en uso. La única prueba limpia es el arranque real del servidor con la observación posterior de ps -o rss= -C java.
Un comando de arranque probado para Paper queda entonces así (los llamados flags de Aikar):
java -Xms6G -Xmx6G \
-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch \
-XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M \
-XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 \
-XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 \
-XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 \
-XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 \
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/minecraft/dumps \
-XX:+ExitOnOutOfMemoryError \
-jar paper.jar nogui
A partir de 12 GB de heap, PaperMC recomienda valores adaptados: G1NewSizePercent=40, G1MaxNewSizePercent=50, G1HeapRegionSize=16M, G1ReservePercent=15 e InitiatingHeapOccupancyPercent=20.
Las dos últimas líneas son la verdadera ganancia y faltan en casi todas las guías. -XX:+HeapDumpOnOutOfMemoryError escribe una imagen completa de la memoria en el momento de la caída, con la que después se puede demostrar la causa. -XX:+ExitOnOutOfMemoryError termina la JVM de inmediato en lugar de dejarla seguir en un estado medio muerto, en el que los jugadores se conectan y pierden progreso. Junto con Restart=on-failure en el archivo de la unidad, de ahí sale un reinicio limpio, véase al respecto Iniciar un servidor Minecraft automáticamente y Crear un servicio systemd.
Tenlo en cuenta: el directorio de dumps tiene que existir y disponer de al menos tanto espacio como -Xmx. Un heap de 8 GB genera un archivo .hprof de 8 GB. Si después el disco se queda lleno, tienes un segundo problema, véase Disco lleno en Linux.
Versión de Java y diferencias entre sistemas
La JVM que uses cambia el comportamiento de forma notable:
- Java 8 usa por defecto el collector paralelo, no G1. Aquí aparece también la variante
java.lang.OutOfMemoryError: GC overhead limit exceeded, que significa que más del 98 por ciento del tiempo se ha pasado en la garbage collection. El registro del GC se activa con-XX:+PrintGCDetails -Xloggc:gc.log. - A partir de Java 9, G1 es el estándar en todas las máquinas con al menos dos núcleos y 1792 MB de RAM. El registro va por el nuevo Unified Logging:
-Xlog:gc*:file=logs/gc.log:time,uptime:filecount=5,filesize=10M. Aquí los flags antiguos se rechazan y el servidor no arranca. - Situación de los paquetes: Debian 13 trae
openjdk-21-jre-headlessyopenjdk-25-jre-headless, pero no Java 17. Debian 12 trae Java 17, pero ni 21 ni 25. Ubuntu 22.04 y 24.04 tienen 8, 11, 17, 21 y 25. Quien necesite una versión concreta que la distribución no conozca, recurre al repositorio de Adoptium (Temurin 8 a 26 para trixie, bookworm, noble y jammy). Detalles en Instalar Java 21 en Debian y Instalar Java 17 en Debian.
La trampa en la que cae casi todo el mundo: las herramientas de diagnóstico jcmd, jmap, jstat y jstack no vienen incluidas en los paquetes jre-headless. Están dentro de openjdk-XX-jdk-headless. Quien gestiona el servidor solo con el JRE se queda sin herramientas justo cuando las necesita:
apt install -y openjdk-21-jdk-headless
dnf install -y java-21-openjdk-devel
La primera línea vale para Debian y Ubuntu, la segunda para AlmaLinux, Rocky Linux y Oracle Linux. Después, jcmd y jmap quedan en /usr/bin/. Compruébalo con command -v jcmd y no con which jcmd: en la instalación mínima de la familia Red Hat falta which, y en EL 10 está obsoleto de todas formas.
Detectar fugas de memoria causadas por plugins
Una fuga de memoria tiene un aspecto distinto al de una simple falta de memoria. La diferencia está en la evolución: con un heap demasiado pequeño, el consumo se estabiliza tras el arranque en un nivel alto y se queda ahí. Con una fuga, el valor base sigue subiendo después de cada garbage collection completa. Ese valor base es precisamente la cifra que importa.
Se puede consultar directamente. Primero averigua el ID del proceso, después fuerza una recolección completa y mira el heap:
pgrep -f paper.jar
jmap -histo:live PID | head -30
jcmd PID GC.heap_info
Aviso importante: jmap -histo:live dispara internamente una recolección completa y funciona también cuando, como arriba, está puesto -XX:+DisableExplicitGC. El comando más obvio, jcmd PID GC.run, no hace nada en esa combinación, porque va por System.gc() y eso es exactamente lo que se ha desactivado. Por experiencia, eso cuesta media hora de confusión.
Anota el valor justo después del arranque, y luego a las dos, seis y doce horas. Si el valor tras la recolección completa sube de forma constante sin que haya más jugadores conectados, es una fuga. La lista de clases del histograma suele indicar ya por dónde van los tiros: cantidades enormes de ItemStack, objetos CraftPlayer de jugadores que salieron hace mucho o un objeto con el nombre de paquete de un plugin.
Resulta bastante más cómodo con el plugin spark, disponible para Paper, Fabric y Forge:
/spark healthreport --memorymuestra de un vistazo el uso del heap, el comportamiento del GC y las zonas fuera del heap./spark heapsummarygenera una clasificación de las clases por consumo de memoria, sin escribir para ello un dump de varios gigabytes./spark gcmuestra la frecuencia y la duración de las recolecciones.
Cuando la sospecha recae sobre un plugin concreto, la comprobación es sencilla: quita el plugin, deja el servidor funcionando 24 horas y vuelve a medir el valor base. Los candidatos clásicos son los plugins que guardan datos de jugadores en maps y no los limpian al desconectarse, además de todo lo que tiene que ver con la edición del mundo y mantiene un historial de deshacer ilimitado.
Swap: salvavidas, no ampliación de memoria
El swap y un heap de Java son enemigos naturales. La garbage collection recorre con regularidad grandes partes del heap. Si aunque sea una fracción de él está en el disco, una pausa de 50 milisegundos se convierte en una de 20 segundos y el servidor pasa por congelado. No cuentes nunca el swap dentro del tamaño del heap.
Aun así, conviene que haya swap, solo que pequeño y poco activo. Actúa como colchón para que los picos breves no disparen enseguida al OOM killer, y recoge páginas poco usadas de otros servicios. Son recomendables entre 2 y 4 GB y una tendencia baja al intercambio:
swapon --show
cat /proc/sys/vm/swappiness
sysctl -w vm.swappiness=10
De forma permanente se define en /etc/sysctl.d/99-swappiness.conf. La configuración en detalle está en Configurar swap y evitar el Out of Memory.
Un caso especial merece atención: -XX:+AlwaysPreTouch combinado con poca RAM. Como en el arranque se toca cada página del heap, la parte sobrante se va de inmediato al swap. El servidor arranca, sí, pero desde el primer tick va tan lento que resulta inservible. Si un servidor arranca de repente con una lentitud extrema después de activar PreTouch, la culpa es de un -Xmx demasiado grande, no de PreTouch.
Como límite duro puedes fijar además MemoryMax en la unidad de systemd, por ejemplo MemoryMax=7G con 8 GB de RAM. Así, si algo se desmadra, el kernel golpea justo al proceso de Minecraft y no a la base de datos ni al acceso SSH.
Cómo sabes que está realmente resuelto
Un reinicio sin caída inmediata no demuestra nada. Comprueba en su lugar estos cinco puntos después de 24 horas de funcionamiento con carga normal:
jcmd PID GC.heap_info: el heap ocupado justo después de una recolección completa debería quedar claramente por debajo del 70 por ciento de-Xmxy mantenerse estable con el tiempo.ps -o rss= -C java: el valor debería estabilizarse en torno aXmxmás 1 a 1,5 GB y no seguir subiendo. Si sube mientras el heap se mantiene estable, la fuga está fuera del heap, normalmente en el metaspace o en los direct buffers.free -h: available no debería bajar nunca de unos 500 MB.swapon --show: la cantidad de swap ocupada debería mantenerse cerca de cero.- El log del GC: las recolecciones completas ("Pause Full") no deberían aparecer prácticamente nunca, y las pausas habituales tienen que quedarse por debajo de 200 milisegundos. Una serie de Full GC seguidos es el aviso seguro del siguiente
OutOfMemoryError, a menudo un cuarto de hora antes.
Además, /tps o bien /spark tps muestran dentro del juego si la tasa de ticks se mantiene estable en 20,0. Un servidor sano en cuanto a memoria conserva ese valor también después de horas.
Cuando Java ni siquiera arranca
Cuatro mensajes literales que suelen aparecer al ajustar los valores de memoria:
Invalid maximum heap size: -Xmx8GBes la errata más habitual. La unidad se escribeG, noGB. Se admitenk,myg, en mayúscula o en minúscula.Initial heap size set to a larger value than the maximum heap sizesignifica que-Xmses mayor que-Xmx, casi siempre porque al copiar solo se ajustó uno de los dos valores.Could not reserve enough space for object heapsignifica que la asignación supera la memoria disponible. En una JVM de 32 bits el tope está en algo menos de 4 GB en cualquier caso, con independencia de la RAM instalada. Compruébalo conjava -version, ahí tiene que aparecer64-Bit Server VM.Unrecognized VM option 'UseG1GC'o similar apunta a una JVM demasiado antigua o equivocada. Algunos flags experimentales necesitan obligatoriamente un-XX:+UnlockExperimentalVMOptionspor delante, y además antes del flag correspondiente en la línea de comandos.
Con qué valores trabaja realmente la JVM al final se puede verificar en cualquier momento. Sin servidor en marcha, a través de los valores por defecto:
java -XX:+PrintFlagsFinal -version 2>/dev/null | grep -w MaxHeapSize
Dos pequeños detalles ahí son intencionados. El 2>/dev/null se traga el banner de versión que la JVM escribe en la salida de error y que, de lo contrario, acabaría en la salida sin pasar por el grep. Y grep -w MaxHeapSize en lugar de grep -i maxheapsize devuelve realmente una sola línea: la búsqueda difusa encuentra además SoftMaxHeapSize, y quien espera un único valor lee entonces con facilidad el equivocado.
Y en el proceso en marcha, a través de los flags realmente activos:
jcmd PID VM.flags
Es la vía más fiable para destapar la situación, sorprendentemente frecuente, de que se ha modificado un script de arranque pero el servidor sigue funcionando con los valores antiguos de un segundo archivo de script.
En resumen: mide primero si el heap es de verdad el problema. Asigna después tanto como necesite el mundo y deja libres al menos 1,5 GB para la JVM y el sistema. Pon -Xms igual a -Xmx, activa el heap dump para cuando llegue el problema y observa el valor base tras la recolección completa a lo largo de varias horas. La diferencia entre "funciona" y "funciona de forma estable" está justamente en ese último paso.
Para la configuración básica del servidor en sí encontrarás los pasos adecuados en Instalar un servidor Minecraft en Debian y Lista de comprobación para un servidor root nuevo.
Preguntas frecuentes
¿Cuánta RAM debo asignar a mi servidor Minecraft?
¿Por qué Xms tiene que ser igual a Xmx?
¿Qué diferencia hay entre un OutOfMemoryError y un proceso que simplemente desaparece con Killed?
¿Cómo reconozco una fuga de memoria causada por un plugin?
¿Sirve de algo más swap contra el error Java heap space?
¿Por qué no encuentro jcmd ni jmap en mi servidor?
2026 KernelHost GmbH. Todos los derechos reservados. Esta guía está protegida por derechos de autor. Su publicación en otros sitios web, aunque sea de forma parcial o modificada, no está permitida sin nuestro consentimiento por escrito. Las citas con indicación de la fuente y un enlace son muy bienvenidas.

