On this page
- Measure before you change anything
- Getting the numbers
- Heap sizing
- Why giving it all the RAM is wrong
- How to pick a number
- Aikar's flags
- What the ones that matter actually do
- What they will not do
- view-distance and simulation-distance
- Paper's entity and redstone config
- Entities
- Redstone
- Hoppers
- Pregenerating chunks
- Finding the plugin that is causing lag
- Related
Most Minecraft lag is not a hardware problem. On a server that is struggling, the cause is almost always view distance, the number of entities being ticked, or one or two plugins doing expensive work every tick, and none of those get better on a faster machine. Measure first, then change one thing at a time.
Measure before you change anything
Two numbers describe how the server is doing, and only one of them is useful on its own.
TPS is ticks per second and it is capped at 20. It only moves once the server is already failing to keep up, so a healthy server sits at 20 whether it has plenty of headroom or almost none.
MSPT is milliseconds per tick, which is the real measurement. The budget is 50 milliseconds. A server at 20 TPS and 45 MSPT is one bad moment away from stuttering. A server at 20 TPS and 12 MSPT has room.
Getting the numbers
/tps on Paper gives you the tick rate and, on recent builds, the tick time.
spark is the profiler to use. Recent Paper builds include it; otherwise install it as a plugin or a mod.
/spark tpsfor tick rate and MSPT percentiles./spark healthfor a summary including memory and, where available, system load./spark profiler start, then wait for the problem to happen, then/spark profiler stop. It uploads a profile and prints a link showing where main-thread time went, broken down by the code that consumed it.
Older Paper builds carry the earlier /timings on and /timings paste tooling.
Paper has moved to spark, so use whichever your build actually has and read the
same way: find the biggest consumer of main-thread time, not the longest list.
Heap sizing
-Xms sets the starting heap and -Xmx sets the maximum. Set them to the same
value. The Java runtime then does not spend time growing and shrinking the heap,
and the flags below can lay the whole heap out at startup.
Why giving it all the RAM is wrong
A machine with 16 GB does not want -Xmx16G. Four separate things go wrong.
- The Java process uses memory outside the heap. Class metadata, thread
stacks, direct byte buffers used for network I/O, and the garbage collector's
own bookkeeping all sit outside
-Xmx. Size the heap to the whole machine and the process still needs more than that. - The operating system needs page cache. Region files are read and written constantly. Free memory the kernel uses to cache them is memory doing useful work. Take it all for the heap and chunk access starts hitting the disk.
- A bigger heap takes longer to collect. Garbage collection pauses scale with how much there is to look at. A pause during a tick is a tick that overran its budget, so an oversized heap converts a memory surplus into a lag spike.
- Overcommitting gets the server killed. If the process asks for more than the machine has, the kernel's out-of-memory killer picks the largest process, which is your server, and terminates it mid-tick.
How to pick a number
Start below what you think you need and let the measurement move it.
- Leave the operating system and anything else on the box a comfortable share of the machine's memory. If you also run a database, a proxy, or a second game server, count those first.
- Run the server under load and watch heap usage with
/spark healthor the memory view in a spark profile. - If used heap sits pinned near the maximum and the profile shows significant
time in garbage collection, raise
-Xmx. - If used heap is comfortably below the maximum, more heap changes nothing. Extra headroom is not extra performance.
Aikar's flags
The default garbage collector settings are tuned for throughput. A Minecraft server wants the opposite: short, predictable pauses, because any pause longer than the remainder of the tick budget is visible to players. The widely used answer is the flag set published as Aikar's flags.
java -Xms4G -Xmx4G \
-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 \
-jar paper.jar --noguiWhat the ones that matter actually do
-XX:+UseG1GCselects the G1 collector, which works in bounded increments instead of stopping everything to do a full pass.-XX:MaxGCPauseMillis=200is the pause target G1 aims for when deciding how much to collect at once.-XX:G1NewSizePercentand-XX:G1MaxNewSizePercentenlarge the young generation. Minecraft produces a great deal of very short-lived garbage, and collecting it while it is still young is much cheaper than letting it be promoted first.-XX:+AlwaysPreTouchfaults in the whole heap at startup, so pages are committed before players arrive rather than during a tick. Startup takes slightly longer in exchange.-XX:+DisableExplicitGCignores a plugin calling for a full collection. One badly behaved plugin can otherwise stall the entire server on demand.-XX:InitiatingHeapOccupancyPercent=15starts concurrent marking early, so G1 keeps ahead of allocation instead of falling back to a full collection.
What they will not do
These flags move pauses around. They do not give the server more throughput, and they will not rescue a plugin that ticks four thousand entities every tick. If your profile shows the time going to plugin code or entity ticking, garbage collector flags are not the fix.
The set above is for a moderate heap. At larger heaps the published flags change: the young generation share and the heap region size both go up. Rather than editing the numbers by hand, generate the set for your heap size and Java version with the flags.sh generator, which is the canonical source for this flag set.
view-distance and simulation-distance
These two settings in server.properties have more effect on tick time than
anything else you can change without touching gameplay.
view-distance is how many chunks out from a player the server sends to their
client. The cost is roughly the square of the radius and it applies per player,
so dropping it from 10 to 8 removes far more work than the difference sounds
like. It affects how far players can see.
simulation-distance is how far out the server actually ticks the world:
entities, redstone, block updates, mob spawning, and crop growth. This is the one
that burns main-thread time. Chunks between the simulation distance and the view
distance are still sent to the client, but nothing in them moves.
Practical order of attack:
- Lower
simulation-distancefirst. It is the expensive one, and players notice it far less than a shorter view. - Keep
simulation-distanceat or belowview-distance. Setting it higher simulates chunks nobody can see. - Change it by 2, restart, and compare MSPT under the same player load. Guessing at a value without measuring is how you end up with a server that looks worse and runs the same.
Two related settings in the same file:
entity-broadcast-range-percentagescales how far away entities are sent to clients. Lowering it cuts outbound update traffic and client-side load on busy servers.network-compression-thresholdis the packet size above which the server compresses. Raising it trades bandwidth for CPU, lowering it does the reverse. Leave it alone unless you have measured a reason.
Paper's entity and redstone config
Paper exposes a large amount of behaviour that vanilla hardcodes. Since Paper
1.19 the files are config/paper-global.yml and config/paper-world-defaults.yml,
with per-world overrides in <world>/paper-world.yml. Older builds used a single
paper.yml. Alongside them sit spigot.yml and bukkit.yml, which Paper still
reads.
Option names and locations move between Paper versions, so find yours rather than trusting a path from a guide. From the server directory:
grep -rn "redstone-implementation" config/ *.ymlEntities
Entities are the usual cause of a slow tick on an established server.
entity-activation-rangeinspigot.ymlsets how close a player must be before each category of entity is ticked at all. Distant mobs stand still instead of consuming time. Lowering the ranges for monsters and animals is the single biggest entity win, at the cost of farms further away from a player running more slowly.merge-radiusinspigot.ymlcombines dropped items and experience orbs that land near each other, so a mob farm produces one stack rather than hundreds of separate entities.spawn-limitsandticks-perinbukkit.ymlcontrol how many mobs of each category can exist per player and how often the spawning routine runs. This is the lever for a server that is full of mobs.- Despawn ranges in the Paper world config decide how far from a player a mob has to be before it is removed. Tightening them keeps the entity count down on a busy map.
Redstone
The Paper world config carries a misc.redstone-implementation option with
alternatives to the vanilla algorithm. The alternatives compute the same dust
power levels with far less work, which matters enormously on a server with large
contraptions, and they change behaviour in a few edge cases that some machines
depend on. Switch it, then test your players' existing builds before you leave it
that way. If the option is not in your config file, your build predates it.
Hoppers
The Paper world config has a hopper section, including an option to skip firing
the inventory move event for every hopper transfer. On a server with a lot of
hoppers this is a large saving.
Pregenerating chunks
Generating new terrain is the most expensive single thing a Minecraft server does, and by default it happens whenever a player walks somewhere new. That is the classic symptom of the whole server hitching when one person explores.
Pregenerating moves that work to a time you choose.
Decide the size first
A pregenerated world is a fixed cost on disk, and terrain that goes on forever means a world folder that goes on forever. Pick a radius your players can live with and check you have the space with
df -hbefore you start.Run the generation
Chunky is the usual tool, and exists as a plugin for Paper and as a mod for Fabric and Forge. From the console:
/chunky world world /chunky center 0 0 /chunky radius 5000 /chunky startExpect high CPU and heavy disk writes for as long as it runs. Do it before the server opens, or during your quietest hours. It can be paused with
/chunky pauseand picked up later with/chunky continue.Set a world border to match
Without a border, players walk straight past the pregenerated area and you are back where you started. The vanilla command takes a diameter, so it is twice your Chunky radius:
/worldborder center 0 0 /worldborder set 10000Repeat for the Nether and the End
They are separate worlds and are generated separately. The Nether is compressed eight to one against the overworld, so its radius is an eighth of the overworld's for the same coverage.
Finding the plugin that is causing lag
Be honest with yourself about this step. Plugins and view distance cause most Minecraft lag. A faster machine raises the ceiling, and a plugin that does expensive work on every tick will rise to consume whatever ceiling you give it.
Profile while the problem is happening
/spark profiler start, wait for the symptom,/spark profiler stop. A profile of a healthy period tells you nothing.Read it top down
The viewer attributes main-thread time by the code that used it, and plugin code carries the plugin's own package name. A plugin at the top of the list, or a scheduled task running every tick, is your answer.
Separate the categories
If the time is not in plugin code, it will be somewhere identifiable: entity ticking, chunk loading and generation, or world saving. Entity ticking sends you to the entity config above, chunk generation sends you to pregeneration, and world saving usually means the disk.
Confirm by removing it
Stop the server, move the suspect jar out of
plugins/, start, and measure again under similar load. This is the only step that proves it. See Installing and managing Minecraft plugins for doing that on a copy rather than on the live server.
Related
Why single-core speed and RAM matter more than core count.
Installing a Minecraft server on LinuxWhere the start command and the service file live.
Installing and managing Minecraft pluginsRemoving, updating, and testing a suspect plugin safely.
BackupsTake one before you start changing config files.