Memory, heap & Java flags
Your plan RAM is not your heap
This is the single most useful thing to understand about a Minecraft server's memory, and it explains most “I bought more RAM and it got worse” stories.
Your plan's RAM is the limit on the whole container. Inside it, Java carves out a heap for the game world, and then needs more memory on top of that for things that are not the heap at all:
| What | Roughly | Grows with |
|---|---|---|
| Heap: chunks, entities, plugin data | What you set | Players, world size, plugins |
| Metaspace: loaded classes | 100–300 MB | Number of plugins |
| Thread stacks | 50–150 MB | Plugins doing async work |
| GC and JVM internals | 100–200 MB | Heap size |
| Direct buffers: networking | 50–200 MB | Player count |
So a 2 GB plan cannot run a 2 GB heap. Try it and the container hits its limit and gets killed, which looks exactly like a crash with no error in the log.
When the container exceeds its memory limit the process is terminated from outside. There is
no OutOfMemoryError, no crash report, often no final log line. The console
just ends and the server restarts. A clean stop with nothing before it is the signature.
Check what your server is actually doing
Open the Console tab and run:
spark heapsummary
Or without spark, the plain version:
/memory
What you want to see is used heap settling back down after each garbage collection. What you do not want is used heap climbing to the maximum and staying there, with the server pausing periodically. That is the JVM collecting over and over because it has nowhere to put anything.
Find your current setting
Open the Startup tab and look at the startup command and variables. One of these decides your heap:
| Flag | Means |
|---|---|
-Xmx3G | Maximum heap is exactly 3 GB |
-Xms3G | Starting heap is 3 GB |
-XX:MaxRAMPercentage=70 | Maximum heap is 70% of the container limit |
The percentage trap on small plans
MaxRAMPercentage looks tidy, and it is the setting most likely to break a small
server. The problem is that the overhead from step 1 is roughly fixed (it does not
shrink with your plan), so the same percentage behaves completely differently at different
sizes.
| Plan | At 95% | Left for everything else | Result |
|---|---|---|---|
| 2 GB | 1,945 MB heap | ~103 MB | Killed, usually within hours |
| 4 GB | 3,891 MB heap | ~205 MB | Killed under load |
| 16 GB | 15,564 MB heap | ~820 MB | Usually survives |
A 2 GB server at 95% leaves about 100 MB for metaspace, thread stacks, GC structures and networking combined, which is less than the classes alone need once you have a dozen plugins loaded. It will die, and the console will show nothing.
-XX:MaxRAMPercentage=70 works across every plan size: 1.4 GB heap on a 2 GB
plan, 11.2 GB on a 16 GB one, always with enough left over. If you prefer to be explicit, set
-Xmx to your plan size minus about 600 MB and forget percentages entirely.
A percentage is read from the container limit at boot, so changing plan silently changes your heap. Downgrading a 16 GB server to 2 GB takes the heap from 11 GB to 1.4 GB: the same world, a fraction of the room. Restart and re-check the numbers after every resize.
Flags worth setting, and ones to leave alone
Minecraft allocates and discards objects constantly, so the collector matters. The widely used baseline is Aikar's flag set, built around G1 and tuned to keep pauses short rather than maximise throughput:
-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
Two adjustments by size:
| Heap | Change |
|---|---|
| Under 4 GB | Drop G1NewSizePercent to 20 and G1MaxNewSizePercent to 30 |
| 12 GB or more | Raise G1HeapRegionSize to 16M |
Set Xms equal to Xmx
Give the JVM its full heap at startup rather than letting it grow. Growing the heap costs pauses at exactly the wrong moments, and on a dedicated server there is nothing else to save the memory for.
Beyond this set, hand-tuning GC flags reliably makes things worse. In particular do not add
-XX:+UseZGC or -XX:+UseShenandoahGC because a benchmark liked them;
Minecraft's allocation pattern is not what they are built for, and nothing in Paper's own
recommendations asks for them.
When the answer is not memory
Before upgrading a plan, be sure the symptom is actually memory pressure. These three get confused with it constantly:
| Symptom | Memory? | Actually |
|---|---|---|
| Steady 15 TPS with players on, no pauses | No | Too much work per tick: fixing lag |
| Total stops of 10 s+, then a catch-up | Usually not | A blocked tick: read the watchdog dump |
| Short regular pauses, heap pinned at max | Yes | GC thrashing: raise the heap or cut what fills it |
| Server disappears, no error, restarts | Yes | Container kill: heap too close to the plan limit |
What fills a heap fastest
More RAM buys time; it does not remove the cause. Usually it is loaded chunks and entities, not players. Worth checking before you pay for a bigger plan:
spark heapsummary mobcaps
Common culprits: view-distance and simulation-distance left high, large
mob farms, hoppers in bulk, and chunk-loading plugins that never unload what they touch. Dropping
simulation-distance from 10 to 6 often does more for both memory and TPS than
doubling the plan.
A safe starting point
If you want numbers to copy rather than a method, use these and move on:
| Plan | Heap | Reasonable for |
|---|---|---|
| 2 GB | -Xms1400M -Xmx1400M | Up to ~8 players, few plugins |
| 4 GB | -Xms3G -Xmx3G | ~15 players, a normal plugin set |
| 8 GB | -Xms7G -Xmx7G | ~40 players, or a heavy modpack |
| 16 GB | -Xms14G -Xmx14G | Large servers, or a proxy network backend |
Startup flags are read once when the JVM launches. Editing the Startup
tab while the server runs changes nothing until you stop and start it, and a
/reload definitely will not do it.
Troubleshooting
I upgraded my plan and nothing improved
Check whether the heap actually grew. With a fixed -Xmx it does not, you are paying for memory the JVM was told never to use. Raise -Xmx to the
new plan size minus about 600 MB and restart. If the heap was never the limit, see step 5;
for a server that is slow rather than pausing, start with
Fixing lag & low TPS.
The console ends mid-line with no error and the server restarts
Classic container kill. Your heap plus overhead is over the plan limit. Lower
-Xmx by 512 MB, or switch to
-XX:MaxRAMPercentage=70, and restart. If it happens within a minute of boot
every time, the heap is set larger than the plan itself. Check for a leftover
-Xmx from a bigger plan.
Java 8 or 11 images seem to ignore my plan size
Older JVMs do not reliably read container limits, so percentage flags behave
unpredictably or get ignored. On those images always set -Xmx explicitly in
megabytes. If nothing requires the old version, moving to a current Java image is worth
doing for the collector alone.
Should I set Xms lower so the server uses less memory when quiet?
No. There is nothing else on the server to hand the memory to, and letting the heap grow
buys pauses during play in exchange for an idle number nobody sees. Set
-Xms equal to -Xmx.