Servidor Minecraft: resolver "java.lang.OutOfMemoryError: Java heap space"
A mensagem java.lang.OutOfMemoryError: Java heap space não significa automaticamente falta de RAM. Como dimensionar Xmx e Xms corretamente, encontrar fugas de memória e usar swap com sentido.
O servidor corre há três horas, depois bloqueia, a tickrate cai para 2 e na consola aparece:
[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(...)
O primeiro reflexo é quase sempre o mesmo: atribuir mais RAM. Em cerca de metade dos casos essa é exatamente a reação errada e, numa parte deles, piora a situação de forma mensurável. Este artigo mostra o que a mensagem significa realmente, como dimensionar -Xmx e -Xms de forma limpa, como distinguir uma fuga de memória de falta real de memória e como reconhecer que a correção se aguentou.
O que a mensagem significa exatamente, e o que não significa
O heap do Java é a área onde a JVM guarda os objetos: chunks carregados, entidades, inventários, dados de plugins. O seu limite superior é definido com -Xmx. Um OutOfMemoryError: Java heap space significa: a JVM quis criar um objeto, o heap estava cheio e a garbage collection não conseguiu libertar espaço suficiente. Isso não diz absolutamente nada sobre a memória livre do sistema operativo. Um servidor com 64 GB de RAM lança a mesma mensagem com igual fiabilidade se estiver definido -Xmx2G e o mundo precisar de 4 GB.
Há que distinguir isto claramente de duas outras falhas, muitas vezes confundidas com a primeira:
- O processo desaparece sem stacktrace, no log aparece apenas
Killedou, no journal,Main process exited, code=killed, status=9/KILL. Foi o kernel, não a JVM. O OOM killer do Linux atuou porque a memória total do sistema tinha esgotado. Pode confirmar isso comdmesg | grep -i "out of memory", onde encontra então uma linha comoOut of memory: Killed process 1337 (java). - O Java nem sequer arranca e comunica
Error occurred during initialization of VM / Could not reserve enough space for object heap. Nesse caso,-Xmxé maior do que aquilo que o sistema consegue sequer disponibilizar.
Esta distinção é o passo mais importante. Na primeira variante há heap a menos, na segunda e na terceira há heap a mais. Quem confunde os casos mexe no parâmetro errado.
Primeiro medir, depois atribuir
Antes de alterar qualquer valor, precisa de dois números: a memória realmente disponível e o consumo atual.
free -h
cat /proc/meminfo | grep -E 'MemTotal|MemAvailable|SwapTotal'
O que é decisivo é MemAvailable, não free. O Linux usa a RAM não utilizada como cache de ficheiros, por isso o valor "free" é quase sempre pequeno e quase sempre irrelevante.
O consumo real do servidor vê-se assim:
ps -o pid,rss,cmd -C java
O valor RSS vem em kilobytes e corresponde à memória que o processo ocupa na RAM. É sempre maior do que -Xmx, e é precisamente neste ponto que a maioria dos guias termina.
Porque "o máximo possível" corre garantidamente mal
Além do heap, a JVM precisa ainda de toda uma série de outras áreas de memória que -Xmx não abrange de todo:
- Metaspace: as classes carregadas. Num modpack com 300 mods chegam depressa aos 300 a 600 MB.
- Stacks de threads: cada thread recebe cerca de 1 MB. Chunk workers, threads do Netty, schedulers de plugins, tudo isso soma 100 a 300 MB.
- Direct Buffers: o Netty processa todo o tráfego de rede através de memória fora do heap. Com muitos jogadores em simultâneo, várias centenas de megabytes são normais.
- Code cache e estruturas do GC: o compilador JIT e a contabilidade do próprio G1 custam grosso modo 5 a 10 por cento do heap.
Como regra prática fiável vale o seguinte: conte com Xmx mais 1 a 1,5 GB para a JVM, mais pelo menos 512 MB para o sistema operativo. Num modpack com muitos mods, antes Xmx mais 2 GB.
Num servidor com 8 GB de RAM isso significa -Xmx6G e não -Xmx8G. Quem atribuir 8 deixa de ter o erro de heap e passa a ter algo pior: um processo abatido pelo kernel sem aviso prévio, a meio da gravação do mundo. O erro de heap é uma falha limpa e documentada. O OOM kill pode deixar para trás ficheiros de região corrompidos.
Há ainda um segundo motivo contra a atribuição máxima: um heap demasiado grande torna a garbage collection mais lenta. O G1 tem de percorrer mais memória, as pausas de um mixed GC ficam mais longas e um engasgo ocasional transforma-se num freeze bem percetível. Acima de cerca de 12 GB, no Minecraft a relação costuma inverter-se para pior. Quem precisar de mais deve dividir o mundo em vez de aumentar o heap.
Regras práticas por número de jogadores e modpack
Estes valores são pontos de partida, não leis da natureza. Pressupõem um mundo de tamanho normal e pré-geração ativa.
| Tipo de servidor | Jogadores | Xmx | RAM no sistema |
|---|---|---|---|
| Vanilla ou Paper, sem plugins | até 10 | 2G | 4 GB |
| Paper com 15 a 30 plugins | 10 a 30 | 4G | 8 GB |
| Paper, grande conjunto de plugins, base de dados | 30 a 80 | 6G a 8G | 12 a 16 GB |
| Modpack leve, até 120 mods | até 10 | 6G | 8 GB |
| Modpack médio, 150 a 250 mods | até 20 | 8G a 10G | 16 GB |
| Modpack pesado, a partir de 300 mods | até 20 | 10G a 12G | 16 a 24 GB |
| Proxy (Velocity, BungeeCord) | qualquer | 512M a 1G | 2 GB |
Duas notas sobre isto. Primeiro, nos modpacks a necessidade de heap escala quase exclusivamente com o número de mods e o tamanho do mundo, quase nada com o número de jogadores. Segundo, view-distance no server.properties é a alavanca mais eficaz de todas: baixar de 10 para 8 poupa muitas vezes mais memória do que 2 GB adicionais de heap, porque o número de chunks carregados cresce de forma quadrática com a distância de visão. Definir simulation-distance em 6 atua adicionalmente sobre a carga de CPU.
Xms igual a Xmx: o aquecimento
-Xms define com quanto heap a JVM arranca. Se aí estiver um valor menor do que em -Xmx, o heap cresce aos poucos durante a operação. Cada aumento significa uma recolha completa da garbage collection e novos page faults no sistema operativo e, como o G1 também deixa o heap voltar a encolher, o jogo repete-se. É precisamente daqui que vêm os famosos engasgos de poucos em poucos minutos nas primeiras horas depois do arranque.
Defina -Xms sempre igual a -Xmx. Com -XX:+AlwaysPreTouch em complemento, a JVM toca uma vez em cada página de memória do heap logo no arranque. O arranque demora assim 2 a 15 segundos mais, consoante o tamanho do heap, mas durante o jogo deixam de ocorrer page faults. O efeito secundário agradável: se -Xmx estiver escolhido demasiado alto, isso nota-se por regra logo no arranque e não só três horas mais tarde, a meio do jogo.
Não confie, porém, num teste rápido com -version. Medido na prática: java -Xms4G -Xmx4G -XX:+AlwaysPreTouch -version correu sem qualquer problema num ambiente limitado a 2 GB, porque a chamada termina antes de o heap estar realmente a ser usado. Uma prova limpa é apenas o arranque real do servidor, seguido da observação de ps -o rss= -C java.
Um comando de arranque comprovado para o Paper fica então assim (as chamadas flags do 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, o PaperMC recomenda valores ajustados: G1NewSizePercent=40, G1MaxNewSizePercent=50, G1HeapRegionSize=16M, G1ReservePercent=15 e InitiatingHeapOccupancyPercent=20.
As duas últimas linhas são o verdadeiro ganho e faltam em quase todos os guias. -XX:+HeapDumpOnOutOfMemoryError escreve, no momento do crash, uma imagem completa da memória, com a qual é possível comprovar a causa mais tarde. -XX:+ExitOnOutOfMemoryError termina a JVM de imediato, em vez de a deixar a correr num estado meio morto no qual os jogadores se ligam e perdem progresso. Em conjunto com Restart=on-failure no ficheiro da unit, daí resulta um reinício limpo, ver a propósito iniciar automaticamente um servidor Minecraft e criar um serviço systemd.
Conte com o seguinte: o diretório de dumps tem de existir e ter pelo menos tanto espaço quanto -Xmx. Um heap de 8 GB gera um ficheiro .hprof com 8 GB. Se o disco ficar cheio a seguir, tem um segundo problema, ver disco cheio no Linux.
Versão do Java e diferenças entre sistemas
A JVM que utiliza altera o comportamento de forma percetível:
- O Java 8 usa por predefinição o collector paralelo, não o G1. Aqui surge também a variante
java.lang.OutOfMemoryError: GC overhead limit exceeded, que significa que mais de 98 por cento do tempo foi passado na garbage collection. O registo do GC faz-se através de-XX:+PrintGCDetails -Xloggc:gc.log. - A partir do Java 9, o G1 é a predefinição em todas as máquinas com pelo menos dois núcleos e 1792 MB de RAM. O registo passa pelo novo unified logging:
-Xlog:gc*:file=logs/gc.log:time,uptime:filecount=5,filesize=10M. As flags antigas são aqui rejeitadas e o servidor não arranca. - Situação dos pacotes: o Debian 13 fornece
openjdk-21-jre-headlesseopenjdk-25-jre-headless, mas nenhum Java 17. O Debian 12 fornece o Java 17, mas nem o 21 nem o 25. O Ubuntu 22.04 e 24.04 têm 8, 11, 17, 21 e 25. Quem precisar de uma versão específica que a distribuição não conheça recorre ao repositório do Adoptium (Temurin 8 a 26 para trixie, bookworm, noble e jammy). Detalhes em instalar o Java 21 no Debian e instalar o Java 17 no Debian.
A armadilha que apanha quase toda a gente: as ferramentas de diagnóstico jcmd, jmap, jstat e jstack não vêm incluídas nos pacotes jre-headless. Estão no openjdk-XX-jdk-headless. Quem opera o servidor com o JRE fica sem ferramentas na hora da verdade:
apt install -y openjdk-21-jdk-headless
dnf install -y java-21-openjdk-devel
A primeira linha vale para Debian e Ubuntu, a segunda para AlmaLinux, Rocky Linux e Oracle Linux. Depois disso, jcmd e jmap ficam em /usr/bin/. Verifique isso com command -v jcmd e não com which jcmd: na instalação mínima da família Red Hat falta o which e, no EL 10, está de qualquer forma descontinuado.
Detetar fugas de memória causadas por plugins
Uma fuga de memória não tem o mesmo aspeto que a simples falta de memória. A diferença está na evolução: com um heap demasiado pequeno, o consumo estabiliza num nível alto depois do arranque e fica por aí. Numa fuga, o valor de base continua a subir após cada garbage collection completa. É precisamente esse valor de base o indicador que interessa.
Pode ser consultado diretamente. Primeiro determinar o ID do processo, depois forçar uma recolha completa e observar o heap:
pgrep -f paper.jar
jmap -histo:live PID | head -30
jcmd PID GC.heap_info
Nota importante: jmap -histo:live desencadeia internamente uma recolha completa e funciona também quando, como acima, está definido -XX:+DisableExplicitGC. O comando óbvio jcmd PID GC.run não faz nada nesta constelação, porque passa por System.gc() e foi exatamente isso que ficou desativado. Pela experiência, isto custa meia hora de confusão.
Anote o valor logo depois do arranque, depois ao fim de duas, seis e doze horas. Se o valor após a recolha completa subir continuamente sem que haja mais jogadores online, trata-se de uma fuga. A lista de classes do histograma mostra normalmente logo o caminho: ItemStack aos milhares, CraftPlayer de jogadores há muito desligados ou um objeto com o nome de pacote de um plugin.
É bastante mais cómodo fazer isto com o plugin spark, disponível para Paper, Fabric e Forge:
/spark healthreport --memorymostra de relance a utilização do heap, o comportamento do GC e as áreas fora do heap./spark heapsummarygera uma lista de classes ordenada por consumo de memória, sem escrever um dump com vários gigabytes./spark gcmostra a frequência e a duração das recolhas.
Se a suspeita recair sobre um plugin concreto, a contraprova é simples: remover o plugin, deixar o servidor a correr 24 horas e medir de novo o valor de base. Candidatos clássicos são plugins que guardam dados de jogadores em maps e não limpam nada quando estes se desligam, bem como tudo o que tem que ver com edição de mundo e mantém um histórico de anulação ilimitado.
Swap: boia de salvação, não ampliação de memória
O swap e um heap do Java são inimigos naturais. A garbage collection percorre regularmente grandes partes do heap. Se apenas uma fração disso estiver no disco, uma pausa de 50 milissegundos passa a ser de 20 segundos e o servidor é dado como bloqueado. Nunca conte o swap para o tamanho do heap.
Ainda assim, deve existir swap, só que pequeno e pouco solicitado. Funciona como amortecedor para que picos curtos não desencadeiem logo o OOM killer e acolhe páginas raramente usadas de outros serviços. Recomendam-se 2 a 4 GB e uma baixa tendência para paginar:
swapon --show
cat /proc/sys/vm/swappiness
sysctl -w vm.swappiness=10
De forma permanente, isso define-se em /etc/sysctl.d/99-swappiness.conf. A configuração em detalhe está em configurar swap e evitar out of memory.
Um caso especial merece atenção: -XX:+AlwaysPreTouch em combinação com pouca RAM. Como no arranque é tocada cada página do heap, a parte excedente vai imediatamente para o swap. O servidor até arranca, mas fica inutilizavelmente lento desde o primeiro tick. Se um servidor passar a arrancar extremamente devagar depois de ativar o PreTouch, a culpa é de -Xmx estar demasiado alto, não do PreTouch.
Como limite rígido adicional pode definir MemoryMax na unit do systemd, por exemplo MemoryMax=7G com 8 GB de RAM. Assim, em caso de descarrilamento, o kernel atinge de forma dirigida o processo do Minecraft e não a base de dados ou o acesso SSH.
Como reconhecer que está mesmo resolvido
Um reinício sem crash imediato não é prova de nada. Verifique antes estes cinco pontos após 24 horas de operação sob carga normal:
jcmd PID GC.heap_info: o heap ocupado logo depois de uma recolha completa deve ficar claramente abaixo de 70 por cento de-Xmxe manter-se estável ao longo do tempo.ps -o rss= -C java: o valor deve estabilizar em cerca deXmxmais 1 a 1,5 GB e não continuar a subir. Se subir enquanto o heap se mantém estável, a fuga está fora do heap, tipicamente no Metaspace ou nos Direct Buffers.free -h: available nunca deve descer abaixo de cerca de 500 MB.swapon --show: a quantidade de swap ocupada deve manter-se perto de zero.- O log do GC: recolhas completas ("Pause Full") não devem praticamente ocorrer e as pausas habituais devem manter-se abaixo de 200 milissegundos. Vários full GCs em série, uns atrás dos outros, são o sinal certo do próximo
OutOfMemoryError, muitas vezes um quarto de hora antes.
Além disso, /tps ou /spark tps mostram dentro do jogo se a tickrate se mantém estável em 20,0. Um servidor saudável do ponto de vista da memória mantém este valor mesmo ao fim de horas.
Quando o Java nem sequer arranca
Quatro mensagens, na sua formulação literal, que surgem tipicamente ao ajustar os valores de memória:
Invalid maximum heap size: -Xmx8GBé a gralha mais frequente. A unidade chama-seG, nãoGB. São permitidosk,meg, em maiúsculas ou minúsculas.Initial heap size set to a larger value than the maximum heap sizesignifica que-Xmsé maior do que-Xmx, normalmente porque ao copiar só se ajustou um dos dois valores.Could not reserve enough space for object heapsignifica que a atribuição excede a memória disponível. Numa JVM de 32 bits o limite fica sempre pouco abaixo dos 4 GB, independentemente da RAM existente. Verifique comjava -version, onde tem de constar64-Bit Server VM.Unrecognized VM option 'UseG1GC'ou semelhante aponta para uma JVM demasiado antiga ou errada. Algumas flags experimentais exigem obrigatoriamente um-XX:+UnlockExperimentalVMOptionsà frente, e isso antes da flag em causa na linha de comandos.
Com que valores a JVM realmente trabalha no fim, isso pode ser verificado a qualquer momento. Sem servidor a correr, através dos valores predefinidos:
java -XX:+PrintFlagsFinal -version 2>/dev/null | grep -w MaxHeapSize
Dois pormenores aí são intencionais. O 2>/dev/null engole o banner de versão que a JVM escreve na saída de erro e que, de outra forma, passaria ao lado do grep e apareceria sem filtro no resultado. E grep -w MaxHeapSize em vez de grep -i maxheapsize devolve mesmo apenas uma linha: a pesquisa difusa encontra também SoftMaxHeapSize e quem espera um único valor lê facilmente o errado.
E no processo em execução, através das flags realmente ativas:
jcmd PID VM.flags
Esta é a forma mais fiável de descobrir a situação surpreendentemente comum em que um script de arranque foi de facto alterado, mas o servidor continua a correr com os valores antigos de um segundo ficheiro de script.
Em resumo: meça primeiro se o heap é sequer o problema. Atribua depois tanto quanto o mundo precisa e deixe pelo menos 1,5 GB para a JVM e para o sistema. Defina -Xms igual a -Xmx, ative o heap dump para a hora da verdade e observe o valor de base após a recolha completa ao longo de várias horas. A diferença entre "funciona" e "funciona de forma estável" está exatamente neste último passo.
Para a configuração de base do próprio servidor encontra os passos adequados em instalar um servidor Minecraft no Debian e checklist para um novo servidor root.
Perguntas frequentes
Quanta RAM devo atribuir ao meu servidor Minecraft?
Porque deve Xms ser igual a Xmx?
Qual é a diferença entre um OutOfMemoryError e um processo que simplesmente desaparece com Killed?
Como reconheço uma fuga de memória causada por um plugin?
Mais swap ajuda contra o erro Java heap space?
Porque não encontro o jcmd e o jmap no meu servidor?
2026 KernelHost GmbH. Todos os direitos reservados. Este guia está protegido por direitos de autor. A sua republicação noutros sites, na íntegra, em parte ou de forma editada, não é permitida sem o nosso consentimento por escrito. Citações com indicação da fonte e ligação são expressamente bem-vindas.

