Symptoms: The server normally runs smoothly, but every so often there are 2-5 second freezes and then it carries on. Players get momentarily "teleported", mobs jump around. These sudden freezes (lag spikes) usually come from a GC pause, a chunk save or a backup.

Possible Causes

  • Stop-the-world GC pause - the heap size is unsuitable or GC flags are missing
  • Chunk save spike - during an automatic save every modified chunk is written to disk at once
  • A backup is running - while tar/zip works the disk IO fills up and the main thread waits to write
  • RAM swap - physical RAM is not enough, so the kernel swaps to disk
  • A non-async plugin operation - a DB query or HTTP request blocking on the main thread
  • Entity / tile entity tick spike - too many hoppers or beacons processed in the same tick

Step-by-Step Fix

Step 1

Catch the moment of the spike with the Spark profiler - filter for the heavy ticks only

bash
/spark profiler --only-ticks-over 50 --timeout 180

Step 2

Turn on GC logs - see the pause durations clearly

bash
# add this to the start script:
-Xlog:gc*:file=logs/gc.log:time,uptime:filecount=5,filesize=10M

Step 3

Keep the heap fixed with Xms = Xmx. Also give it a MaxGCPauseMillis=200 target

bash
-Xms8G -Xmx8G -XX:+UseG1GC -XX:MaxGCPauseMillis=200

Step 4

Apply Aikar's Flags in full. Half of them will not end the spikes. Use the whole set

bash
# Reference for Aikar flags:
# https://docs.papermc.io/paper/aikars-flags

Step 5

Spread the chunk save out - do not save the whole world at once

bash
# config/paper-global.yml
chunk-saving:
  max-chunks-to-save-per-tick: 4

Step 6

Move your backup script to a low-traffic hour (03:00 for instance). Running tar while players are online causes a big spike

bash
crontab -l | grep backup
# new schedule example:
# 0 3 * * * /opt/scripts/mc-backup.sh

Step 7

Check RAM swap usage - if swap > 0 there is a problem

bash
free -m
vmstat 1 5  # si/so columns should be 0

Step 8

Reduce hopper chains and beacon clusters. Install LagAssist / FarmLimiter if you need to

bash
# Find the problem chunks in the Entity tick section of the Spark profiler report

Step 9

Watch out for non-async plugin operations - they show up in the Spark timings report. Either update the plugin or ask for a ConnectionPool to be added

Step 10

If the problem persists, upload the profile report to the KEYDAL Spark Analyzer tool; you get a free automated analysis: /oyun/araclar/spark