JoinGG.cc
Features

Memory Leaks and Restart Schedules: Why Your Server Degrades

Your server ticks fine at 8am, crawls by 10pm. Separate a real memory leak from GC pressure and entity buildup, then pick a restart window that keeps players.

By SweetMask · · updated · 17 min read

A server that is fast on boot and unplayable twelve hours later is accumulating something, not straining under load. Four causes look identical from outside: a real reference leak, garbage collector pressure, entity and chunk buildup, and handle exhaustion. Work out which one you have, then use a scheduled restart as deliberate mitigation.

Published 1 October 2026 · JoinGG · Guides

Is it load, or is it accumulation?

Before you touch a single flag, settle one question: does the server get slow because more people are on it, or because it has been running longer? Those are different faults with different fixes, and almost everyone assumes the first when they have the second.

The test costs nothing. Record your millisecond-per-tick figure, the number the tick rate guide explains how to read, at a fixed player count in the morning, then again at the same count in the evening. Twenty players at 09:00 costing 18 ms per tick and twenty players at 21:00 costing 44 ms is accumulation: the workload is identical and the machine is not. If instead you sit at 18 ms with twenty players and 44 ms with sixty, that is honest load, and the answer lives in your hardware and server software rather than in this guide. Get both readings before you change anything else.

Two follow-up checks make it firm. Watch what happens overnight: a load problem recovers on its own when the last player disconnects, while an accumulation problem still sits at 40 ms with nobody online. Then restart, and watch the number snap back at the same player count. If a restart fixes it and nothing else does, the fault is state held inside the running process, which narrows the field enormously.

Then start recording, because memory problems are trends and you cannot diagnose a trend from a single reading. Sample resident set size and MSPT every five minutes and keep a week of it. Pterodactyl graphs this for you; docker stats, or a cron line writing ps -o rss= -p PID to a file, does the same job. The admins whose servers are still running years later are the ones who can produce a graph rather than a feeling.

What a real memory leak looks like in a plugin

In a garbage-collected runtime, a leak is not memory that got lost. It is memory the collector may not reclaim, because something still holds a reference to it. The collector is working perfectly. It is being lied to.

The pattern behind most Minecraft plugin leaks is a static map keyed by player: an entry added on join, never removed on quit. Two hundred visitors a day at a few kilobytes each sounds harmless until you see what those entries point at. Storing a Player object rather than a UUID pins the entity, its inventory, the chunk it stood in and sometimes the whole world object, so one forgotten reference can retain hundreds of megabytes. Caches with no eviction policy, scheduler tasks never cancelled in onDisable, and listeners re-registered on every world load are the same shape.

The other reliable cause is /reload. It leaves the old classloader reachable, so the previous copy of every static field survives alongside the new one. Do it four times in an afternoon and you are carrying five copies of the plugin's state. Treat it as development-only and restart properly.

How to prove it rather than guess

Turn on GC logging with -Xlog:gc*:file=logs/gc.log:time,uptime:filecount=5,filesize=10M and read heap occupancy immediately after each collection. That post-collection number is the floor, and the floor is the whole diagnosis: normal behavior is a sawtooth returning to the same floor all day, while a leak climbs 200 or 400 MB an hour and never comes back down. Then take a heap dump with jcmd PID GC.heap_dump /path/dump.hprof and open it in Eclipse MAT. The dump pauses the JVM for seconds and writes a file the size of your live heap, so do it at 04:00. The leak suspects report usually names one plugin's collection outright.

Without tooling, bisect: disable half your plugins, run a day, compare the hourly growth rate, repeat. Three or four days on a twenty-plugin server, and it always works, because you are comparing a rate rather than hunting a single event.

The same bug outside the JVM

Leaks are a reference problem, not a Java problem. On the live FiveM server list the equivalent is a Lua table that grows forever: player data never cleared on drop, or a handler appending to a log array. Run resmon 1 in the console, watch the memory column per resource, and use restart resourcename to confirm a suspect without dropping everyone. On Source engine servers it is a handle leak, where a plugin creates an ArrayList each round and never closes it, so the process grows by a predictable amount at every map change. The comparison of SourceMod, AMX Mod X and Oxide covers each framework's own diagnostics.

Why is the heap full when nothing has leaked?

Heap pressure is the case people misdiagnose as a leak most often. Nothing is retained wrongly. The server allocates so much short-lived garbage per tick that the collector runs constantly, and the time it spends running comes out of your 50 ms budget. Resident memory looks stable; the tick graph looks terrible.

Recognize it by shape. A leak produces a slowly rising MSPT baseline. Garbage collection produces a flat baseline with sharp spikes at regular intervals, typically every 30 to 90 seconds, each lasting a few ticks. Line the spike timestamps up against your GC log; if they match, stop looking for a leak.

Why -Xmx equal to all your RAM makes it worse

Setting the maximum heap to the machine's total memory feels generous and is actively harmful, for three separate reasons.

Leave 25 to 30 percent of physical RAM outside the heap: -Xmx10G to -Xmx12G on a 16 GB machine, -Xmx6G on 8 GB, with -Xms set to the same value so the heap does not repeatedly commit and release regions. Get it wrong and resident memory climbs past your ceiling, the kernel out-of-memory killer takes the largest process, and the server vanishes with nothing in its own log. Check dmesg | grep -i oom before blaming a plugin. Sizing the box is covered in the guide to game server hardware specifications.

What G1 is actually doing to your ticks

G1 splits the heap into regions and aims at a pause target rather than minimizing total work. Two consequences matter. -XX:MaxGCPauseMillis=200 is a goal, not a promise, and 200 ms is four ticks. And lowering that target does not make pauses free; it makes G1 shrink the young generation, raising collection frequency and total overhead. Most Paper servers do better with a large young generation, which is why the common flag set specifies -XX:G1NewSizePercent=30, -XX:G1MaxNewSizePercent=40 and -XX:G1HeapRegionSize=8M: objects bigger than half a region become humongous allocations, and chunk palettes cross that line on the default sizing.

Allocation rate is a software property as much as a hardware one, so the cheapest fix is often changing what you run. The comparison of Paper, Purpur and Fabric is the right starting point; moving off Spigot alone cuts both tick cost and garbage per tick. ZGC and Shenandoah rarely pay off below about 16 GB of heap, because sub-millisecond pauses are bought with throughput and extra memory.

Entity and chunk buildup is a game-state problem

The third cause is not a memory fault at all. Memory looks fine. What has grown is the amount of world the server simulates every tick, and it grew because your players built things.

Every entity in a loaded chunk costs tick time whether or not anyone can see it: dropped items from a mob farm running unattended, armor stands used as shop decoration, boats and minecarts abandoned across the map, hoppers polling their contents, and villagers, the most expensive common entity because of pathfinding. A world ticking 4,000 entities in its first week can be at 40,000 after a busy summer on a 40-player Minecraft server, and entity count is the most predictive number for how bad evenings feel.

The knobs that help are configuration rather than hardware: item merge radius, alternative despawn rates for common drops, per-chunk entity save limits, hopper check intervals, mob spawn ranges and per-category spawn limits. Use /paper entity list world to find the chunks carrying the load and /paper chunkinfo to see how many chunks are loaded versus actually ticking. Chunks stay resident because something holds a ticket for them, usually a chunk loader plugin, a forceload someone set months ago, or a spawn radius left at a large default.

One distinction changes your restart strategy. In Minecraft, entities are written into region files, so a restart does not delete them; you come back up with the same 40,000 entities and the same tick cost. On Source-engine games the opposite holds, because a map change wipes the entity list. That is why prop accumulation on Garry's Mod servers self-corrects at map rotation while a Minecraft item farm does not, and why the fix there is sbox_maxprops and a cleanup on round change rather than a restart schedule. The Garry's Mod server guide covers the gamemode-specific limits.

FiveM sits between the two. Vehicles spawned by job scripts and never deleted stay networked, each costing sync bandwidth and server CPU, and OneSync has a hard entity ceiling you can reach before you run out of anything else. Sweeping abandoned vehicles on a timer is standard practice on every large roleplay server for exactly this reason.

File handles, threads and database connections run out too

The fourth cause produces the strangest symptoms, because the server does not get slow. It works perfectly for existing players and refuses to do one specific new thing.

File descriptors are the classic version. Every player socket, every open region file, every log file and every database connection consumes one, and the default limit of 1,024 on many distributions is far too low. A 100-player Minecraft server with a normal plugin set can sit between 800 and 1,200 descriptors in ordinary operation. Check the count with ls /proc/PID/fd | wc -l and the ceiling with cat /proc/PID/limits, then set LimitNOFILE=65535 in the systemd unit, remembering that a container has its own limit. What matters is the difference between a high water mark and a leak: if the count climbs monotonically over a day and never falls, some code is not closing what it opens, and new connections will fail while everyone already online is fine.

Database connection pools fail the same way and are more common on roleplay servers. An ESX or QBCore stack on oxmysql typically runs a pool of ten to twenty connections, and one query holding a lock, or a script acquiring connections inside a loop, drains it. Movement stays smooth because the game loop is unaffected, but every money transaction, inventory action and character save hangs, and to players it looks like the server froze. Check SHOW PROCESSLIST first, and expect a missing index on a table like owned_vehicles that grew past a million rows. This overlaps with the guide to fixing FiveM server lag, because the two problems arrive together.

Two more are worth knowing. Thread leaks, where a plugin creates an executor per event and never shuts it down, cost about 1 MB of off-heap stack each and never appear in a heap dump; watch /proc/PID/status and treat several hundred threads as suspicious. And ephemeral port exhaustion, where a script opens a fresh outbound HTTP connection for every kill, which ss -tan state time-wait | wc -l reveals in seconds. The fix there is connection reuse, not kernel tuning.

How do you tell which one you have?

Four instruments separate all four causes. The tick graph gives the shape: rising baseline, periodic spikes, or a step change. Resident set size over time separates memory growth from tick growth. The GC log gives the post-collection floor, the one number that distinguishes a leak from pressure. A profiler gives attribution: /spark profiler --timeout 300 on Minecraft, resmon on FiveM, the server's own profiler on Source games. Spark has replaced Timings on current Paper builds and names the plugin and method rather than a category.

Run them in that order. Most admins start with the profiler, which answers the wrong question when the cause is a GC pause: it blames whichever method was running when the world stopped.

Pair the graph with an event log, so a step change on Tuesday matches the plugin you updated on Tuesday. That habit is most of what the guide to monitoring and uptime for game servers is about.

Scheduled restarts are a mitigation, not an admission of defeat

Restarting on a schedule has a bad reputation it does not deserve. Every long-running process accumulates something, even ones with no bugs: heap fragmentation, cache churn, drifted connection pools. Large networks in every game restart at least daily. Doing it deliberately is operations; doing it because the server crashed is not.

What a restart is not is a fix. It resets the clock on the growth rate without changing the rate. If your post-GC heap floor climbs 400 MB an hour, a twelve-hour cycle still spends its last three hours degraded. Measure the rate, then set an interval that keeps you inside the comfortable part of the curve.

What a restart actually costs you

A Paper server needs two to five minutes after boot before it is genuinely fast: chunks load from disk, the JIT has not optimized the hot paths, plugins run startup queries. A FiveM server starts every resource, then takes a wave of simultaneous character loads. So a restart with 60 people online is not thirty seconds of interruption; it is five to eight minutes of a worse-than-normal server, plus everyone who took the disconnect as a cue to leave.

That last part is the expensive one. Restarting during your peak hour costs the exact players you spent months attracting, and returning players are harder to win than first-time joins; the reasons they fail to come back are the subject of the breakdown of why nobody joins your server.

Pick the window from your own player curve

Do not copy someone else's restart time. Pull your own hourly concurrent numbers for a week and find the hour lowest on all seven days, not just on average: one Saturday spike at 05:00 ruins a window that looks perfect in the weekly mean.

Global figures are a sanity check, not an answer. Across all tracked games the busiest measured hour is Saturday at 18:00 UTC with 242,719 players and the quietest is Wednesday at 06:00 UTC with 73,158, but those aggregate very different audiences. The FiveM population statistics, around 72,600 players across 1,854 servers, follow a different daily shape from the Minecraft population data at roughly 56,400 across 4,230. Use the cross-game statistics pages for the overall pattern, then trust your own numbers; JoinGG polls hourly and retains seven days, exactly the resolution this decision needs, and the measurement methodology explains what a sample does and does not capture.

How often, and how to set the interval

Derive the interval instead of picking a round number. Take your MSPT at a fixed player count, or your post-GC heap floor, and measure the growth over 24 hours.

  1. Under 15 percent growth in 24 hours. One restart a day, in your trough. This is where a healthy server lives.
  1. Doubling in 12 hours. Two restarts a day, and treat the leak hunt as active work rather than something for later.
  1. Doubling in under 6 hours. Restart every four to six hours as a stopgap and prioritize the cause. Something is badly wrong.
  1. Below four hours apart. Stop. The cold-start penalty now eats a real share of your uptime and you are running a permanently warming-up server. Roll back the last change instead.

Two traps. Do not measure the interval from the last start, because a 24-hour timer drifts a few minutes each cycle until it lands on your peak; pin it to a clock time. And do not write a script that waits for the server to be empty, because on a healthy server that condition never becomes true.

Warning countdowns and automated restart tooling

A restart nobody expected reads as a crash. An announced one reads as maintenance, and players plan around it. Use a ladder rather than one warning, in chat and your Discord announcement channel at once so people away from the keyboard still see it:

  1. 15 minutes. The one that matters. Long enough to finish a run, bank an inventory, or decide not to start something new.
  1. 10 and 5 minutes. Reinforcement, and the point where staff stop approving anything long-running.
  1. 2 and 1 minute. Get to safety. Land the aircraft, leave the dungeon, stop the heist.
  1. 30 and 10 seconds. Final notice, so the disconnect is expected rather than surprising.

Shut down cleanly at the end of that. Send save-all then stop over RCON for Minecraft, or quit in the FiveM or Source console, and allow 60 to 120 seconds to exit before anything sends a kill signal. A hard kill skips world saves, plugin shutdown handlers and pending database writes, and will eventually cost a player their inventory. TimeoutStopSec=120 gives it room in a systemd unit.

The tools people actually use

Keep crash restarts and scheduled restarts separate. Automatic restart on failure is a good safety net and a terrible painkiller: a server out-of-memory killed every twenty minutes and coming straight back looks, from outside, like constant outages. Alert on restart frequency, not only downtime. Fixing a leak is also cheaper than the plan upgrade it pushes you toward, the underlying point of the breakdown of game server hosting costs: 32 GB bought to hide a 400 MB per hour leak is a bill you pay every month forever.

Frequently asked questions

Is a nightly restart a sign that something is wrong with my server?

No. Long-running processes fragment their heap, fill caches and hold state nobody planned for. Most large networks restart at least daily. It becomes a problem only when the restart hides a fault you could fix, or when the interval keeps shrinking because the growth rate is getting worse.

How do I tell a memory leak from normal garbage collection?

Read the floor, not the peak. Enable GC logging and record how much heap is still in use immediately after each collection. Normal operation gives a sawtooth with a flat floor that always returns to roughly the same number. A leak gives a floor that creeps upward all day and never comes back.

Should I set -Xmx to all the RAM in the box?

No, and it causes more outages than any other single setting. The JVM needs off-heap memory for threads, metaspace and network buffers, and the operating system needs page cache for region files. Leave 25 to 30 percent of physical RAM outside the heap: on a 16 GB box, 10 to 12 GB.

Will adding more RAM fix a server that slows down through the day?

Only if you were genuinely short of heap. If a plugin is holding references, extra RAM buys a few more hours before the same wall. If the cause is entity or chunk accumulation, it changes nothing at all, because that problem costs tick time rather than bytes. Diagnose first, then spend.

What time should I schedule the restart?

The hour when your own concurrent player count is lowest across all seven days, not the global trough. Across every game JoinGG tracks, the quietest measured hour is Wednesday at 06:00 UTC with 73,158 players and the busiest is Saturday at 18:00 UTC with 242,719. Your audience may sit hours from both.

Do restarts hurt how my server appears on server lists?

A short restart mostly costs you one sample. JoinGG polls every listed server hourly and keeps seven days of history, so a two-minute restart usually falls between sweeps and a ten-minute one may record a zero. Repeated crash-loop restarts do the real damage, leaving gaps across consecutive hours.

Related reading

Degradation is invisible to you and obvious to everyone else. You see a graph; a player sees a server that was good last week and feels bad tonight, and they do not file a report, they just stop coming. JoinGG polls all 11,561 listed servers hourly, which is enough to show whether the schedule you just built held your evening population or only flattened your MSPT.

memory leak game serverrestart schedulejvm heap sizeg1gc pauseminecraft server lag over timeentity accumulation

More