VoxelBench 2.0.0
See what lags your server and what fills its memory — on measurements you can trust VoxelBench 2.0.0 brings together everything since 1.8.0. If you are coming from 1.8.0, everything below is new to you. It adds two diagnostic tools to the plugin. /bench profile shows which…
See what lags your server and what fills its memory — on measurements you can trust
VoxelBench 2.0.0 brings together everything since 1.8.0. If you are coming from 1.8.0, everything below is new to you.
It adds two diagnostic tools to the plugin. /bench profile shows which
plugins, which parts of the server and which methods keep your tick threads
busy, using Java Flight Recorder — built into Java, nothing to download.
/bench memory shows which plugins fill the heap, takes a full heap dump when
you need one, and analyses that dump to name the plugin holding the memory.
A profile or a memory report can be shared by public link, like spark, or kept
in your voxelbench.com account; a heap dump never leaves your server.
Remote monitoring is rebuilt: you turn it on from the game, the voxelbench.com dashboard sees each interval as a whole instead of one instant out of sixty, follows real time on a lagging server, and on Folia measures every region on its own. Tick, garbage-collection and CPU metrics are measured by VoxelBench itself from startup, without spark.
And the benchmark was audited against its own claims. Several hardware metrics
measured their own instruments more than the hardware, the standard benchmark
could be reshaped by the measured server's configuration, most permissions were
declared but never checked, /bench test built and dug in the world you stood
in, and on Folia auto-bench did not run at all. All of that is fixed.
Scores are not directly comparable with 1.8.0 for memory and disk results, for two gameplay tests, and on servers that lag under load; the standard gameplay load itself is unchanged. Compare 2.0.0 runs with 2.0.0 runs — the details are in the Upgrade section.
A profiler in the plugin
/bench profile [seconds]samples the tick threads — the main thread, or every region thread on Folia — and prints, as percentages, which plugins and which parts of the server they spend their time in, and the busiest methods. Self counts the code on top of the stack, including the server code a plugin calls; inclusive counts anything below it. Libraries a plugin declares inlibraries:and plugins declared withpaper-plugin.ymlcount for that plugin.- A ring buffer that catches lag by itself.
/bench profile ring onkeeps the last minutes of samples. When the server lags — 40 ticks in a row of 100 ms or more, or a single tick of one second, by default — VoxelBench dumps and analyses that window on its own, at most once every five minutes. You read the profile of a lag spike that already happened. - In the menus and on the web dashboard. A Profiler button in the main menu starts a capture (30, 60 or 120 s, then keep, share or upload it) and controls the ring buffer; the Reports screen gets a Profiles archive — each profile with its plugins, methods and warnings, and buttons to share, upload, remove the link or delete it. The embedded web dashboard shows the same archive, read-only.
- Profiles stay on your server, next to your reports in
plugins/VoxelBench/reports/profiles/, with their own retention limit. - Benchmarks are never profiled. During
/bench start, a stress-limit, tier or auto-bench run, or a test whose result is sent to voxelbench.com, captures are refused and the ring buffer stops; profiling resumes 30 seconds after the run. - Results are sample counts and percentages, never durations: the sampler cannot promise how often it fires, so turning samples into milliseconds would invent precision.
Share it, or keep it in your account. Nothing leaves the server unless you
ask, and each command sends one profile right away — last names the newest,
preview shows what would leave without sending anything, and adding the verb
to a capture sends its result as soon as it is saved (/bench profile 30 share).
/bench profile share <id>: a public link, like spark. The profile is published behind an unlisted link that anyone who has it can open, for 7 days, without any account. What leaves is a cleaned copy: IP addresses, host names, URLs, file paths, UUIDs, tokens and the names of online players, worlds, the OS user and the machine are removed, and the chat says what was removed./bench profile unshare <id>deletes the link before it expires. Only official builds can share./bench profile upload <id>: private, to your account. On a server linked to voxelbench.com, the profile is kept in the owner's account, as it is.- Neither ever happens automatically or during a benchmark.
The profiler is marked experimental and needs Java Flight Recorder:
/bench profile status says when a minimal Java runtime or the JVM options
leave it out. Permissions voxelbench.profile, and on top of it
voxelbench.profile.share and voxelbench.profile.upload, operators by default.
Memory inspection
/bench memory is what spark's heapsummary and heapdump do, built in, plus
an analysis of the dump that spark leaves to external tools. A summary or an
analysis stays on your server unless you share or upload it; a heap dump never
leaves it.
/bench memory summarycounts the objects of every class on the heap and attributes them to each plugin, the server or Java. It freezes the server about a second for 1.5 GB of heap in use, two for 3.5 GB (half that withall, which skips the garbage collection but counts garbage too), and saves each summary next to your reports, inreports/memory/./bench memory dumpwrites a full heap dump (.hprof) to open in a memory analyser. It freezes the whole server while it writes — from seconds to minutes depending on heap size and disk speed — so it first shows the expected freeze, the file size and the free disk space, and runs only once you confirm. Addgzipto compress it afterwards, with the server running.- A dump that would get the server stopped is refused. Past the watchdog
limit (
settings.timeout-timeinspigot.yml, 60 s by default), the server considers itself crashed and stops. When the expected freeze reaches that limit, the dump is refused with the reason and the way out: raise the limit, or addforceif the server may stop — the preview then repeats the warning in red, andforcelifts nothing else. - A heap dump holds everything in memory — tokens, plugin passwords,
players' data. It is written to
plugins/VoxelBench/heapdumps/, outside the reports folder, readable only by the server's account; at most two are kept, and VoxelBench has no command, button or automatic path to send one. /bench memory analyzereads the dump for you — or addanalyzeto the dump command to do both in one go. A summary counts a plugin's cachedbyte[]and strings for Java; the analysis follows the references and shows the memory each plugin retains, the biggest leak suspects and the chain of fields that keeps each one alive. In our tests, a plugin caching 500 MB showed 0 % in a summary and about half the heap in the analysis, found in 15 to 40 seconds for a 1.5 GB dump.- The analysis never runs inside the server. It runs in a separate Java process at the lowest priority, with one CPU core; TPS stayed at 20 during our tests on a 12-thread machine (on 2 CPU threads or fewer, where it competes with the server, the chat warns first). Before it starts, VoxelBench checks the free memory and the container's memory limit, so the analysis cannot get your server killed for lack of memory: when the full analysis does not fit, a quick one runs instead (sizes per plugin, worlds, chunks and entities in memory, duplicate strings), and when not even that fits, it is refused with the figures. The report holds class, field and plugin names and sizes only, never a value from the heap.
- Big heaps get a full analysis too. The memory of the full analysis follows
the number of objects (about 90 bytes each), not the file. When it does not
fit, the analysis keeps its object graph on disk, in work files it removes
afterwards: about half the memory for exactly the same report, two to three
times slower (60 s instead of 26 s for a 2.2 GB dump of 36 million objects in
our tests), and down to a quarter of the memory (0.8 GB, 2.5 minutes) as a last
resort.
memory-inspection.analysis.disk: falseturns it off. - What one plugin retains. When you suspect one plugin,
/bench memory analyze last retained <plugin>answers how much it retains — what would be freed without it — and the heaviest classes it holds, with a fraction of the memory of a full analysis. - Fewer false alarms. The static state of the server's own classes, which retains 10 % of a small heap on its own, is shown apart instead of as a leak suspect — recognised from the shape of the heap, so Spigot, Paper, Folia and their forks behave alike — while a single class whose static cache keeps growing is still a suspect. Plugins are recognised even when a fork or a hybrid server loads them with its own class loader. And in a full analysis, duplicate strings count only the copies still in use.
- Safe on hosting panels. The analysis shares the container's memory, CPU
and disk limits with your server. When the server's heap leaves no room (the
default Pterodactyl setup gives it 95 % of the container), the chat says which
-Xmxwould. Setmemory-inspection.host-disk-limit-gbto your plan's disk limit and dumps and work files count against it — a panel stops a server that goes over, and VoxelBench warns when it sees one without the setting. With a CPU quota of 2 cores or less, the analysis takes only 30 % of one core, so the quota stays with your server. - Share a report, or keep it in your account — like a profile.
/bench memory share <id>publishes a cleaned copy of a summary or an analysis behind an unlisted public link, for 7 days, without any account: plugin, class and field names, sizes and leak suspects stay, while addresses, paths, UUIDs, script names, the names of players, worlds, the OS user and the machine, the time zone and the machine's memory figures are removed./bench memory upload <id>keeps the report, as it is, in the account of a linked server.last,previewand the verb added to the command that makes the report work as for profiles —/bench memory summary live share,/bench memory analyze last upload,/bench memory dump live analyze share— and for a dump, the analysis leaves, never the dump./bench memory unshare <id>deletes the link. Never automatic, never during a benchmark; only official builds can share. - In the menus: a Memory button next to the Profiler takes a summary or analyses the last dump, and a Memory tile in the Reports screen lists the saved summaries and analyses and shows, for each, which plugins hold the memory, with buttons to preview, share, upload or remove the link. Heap dumps stay command-only.
- None of this runs during a benchmark. HotSpot JVMs only (OpenJ9 has neither
the histogram nor the dump). Permissions
voxelbench.memory, and on top of itvoxelbench.memory.dumpfor dumps and their analysis,voxelbench.memory.shareandvoxelbench.memory.uploadto send a report, operators by default. VoxelBench's own footprint went down too: it now keeps its language files in about 1 MB of heap instead of 6.
Forms, and confirmations without retyping
- Forms instead of options. Typed alone,
/bench profile,/bench memory summary,dumpandanalyzeopen a form — duration, interval, live or all objects, gzip, analysis mode, which dump, keep, share or upload — that builds the command for you: a native dialog on Paper 1.21.6 and later, an inventory screen elsewhere. Only what your permissions andconfig.ymlallow is offered, and a dump still shows its preview and asks for confirmation. - Each form reopens on your last choices, even after a restart. Keeping, sharing or uploading the result, forcing a dump and which dump to analyse always start from their default: nothing leaves because you chose it last time.
/bench confirmconfirms your last preview — a dump, or a profile or reportpreview— from the console too, with all the checks of the full command. Players also get a Yes/No window with the preview (confirmation.popup: falseturns it off).
Remote monitoring: on from the game, the whole interval, in real time
Turning on remote monitoring — the live dashboard on voxelbench.com — meant
editing config.yml and restarting; enabling it before the site was ready
switched it off for good, without a word in game. And what it sent was one
instant of each minute, counted in ticks.
/bench monitor remote on|off|status(permissionvoxelbench.monitor.web).onlists what will be sent, checks in with voxelbench.com right away and tells you what is left to do: nothing, enable monitoring for this server on the site, check your plan, or link the server again.statusshows the link, whether monitoring is sending, paused (and why) or stopped, and what the site kept or refused.- Any order works. While the site is not ready, monitoring pauses and checks
again on its own — every minute, or every 10 minutes when your plan does not
include it.
/bench reloadapplies its settings, and the plugin never rewritesconfig.ymlitself. - Each snapshot describes its interval, not one instant. Each snapshot now carries the interval's TPS, the average, 95th-percentile and worst tick time, the ticks over the 50 ms budget and the seconds spent below 18 TPS. A frozen server reports 0 TPS; a server not measured yet reports nothing instead of a made-up 20.
- Real time, even when the server lags. Snapshots, uploads and the heartbeat were counted in ticks: at 10 TPS a one-minute snapshot became a two-minute one, alert rules went quiet exactly when TPS collapsed, and a lagging server could show as offline. They now follow the wall clock, and uploads happen every minute by default.
- Folia: every region, on its own. On Folia the TPS came from the global
region, which is almost idle. Every region Folia ticks is now measured — farms
and chunk loaders without players included — and listed worst first with its
world, centre block (as Folia's
/tpsshows it), chunks, players, entities, TPS and tick times. The headline figures carry the worst region; averages across regions come alongside. - A frozen server is visible, a sleeping one is not an alarm. The heartbeat
says how long ago the server last ticked. Since Minecraft 1.21.2 an empty
server pauses after
pause-when-empty-seconds: the heartbeat says so instead of looking frozen, and never while a player is online. - Memory that explains a crash: heap still used after garbage collection, the memory the process really uses, and the container's memory limit — a process close to it is killed by the system without any Java error.
- Nothing lost on the way. A full buffer no longer drops a snapshot, a failed upload is retried in order, and the last minutes before a shutdown are sent before the plugin stops. Turning monitoring off is announced, so the dashboard does not raise an "offline" alert, and an event category your plan does not include is silenced alone instead of being refused on every event.
- The
remote-monitoring.eventssettings, which had no effect at all, now apply, and the GC major event counts only real major-collection pauses.
Tick, GC and CPU metrics without spark
VoxelBench now measures every tick from startup, whether or not spark is installed: tick durations from the server's own tick event on Paper and its forks, from the main thread's CPU time on Spigot. Garbage collections are read one by one — major or minor, longest pause, collector and cause — with an estimated allocation rate.
- The embedded dashboard's
/api/metricsgains aperformancesection with 10-second, 1-minute, 5-minute and 15-minute windows. - New PlaceholderAPI placeholders:
%voxelbench_mspt_p50%,_p95,_p99,_peak(with an optional_10s,_1m,_5mor_15msuffix),%voxelbench_tps_10s%to%voxelbench_tps_15m%, and%voxelbench_tick_source%.
Measurements you can trust
Memory latency measured the clock. The loop surrounded a single byte load with two clock reads, ten million times over. Reading the clock alone costs about 41 ns; the published figure was 48 ns — roughly 7 ns of memory signal. It also could not tell cache from RAM: the same code returned 47, 46 and 42 ns on a 256 KB, an 8 MB and a 512 MB buffer. The new measurement walks a chain where each load depends on the previous one: on the same three buffers it returns 14, 105 and 199 ns — what a memory hierarchy actually looks like.
- Random 4K disk I/O measured the page cache. Nothing bypassed the operating system's cache, and the reads targeted the blocks written seconds before. The 4K phases now use aligned direct I/O, and are published only when the cache bypass actually succeeded.
- Four timed loops were timing a random number generator. Memory sequential write spent 69 % of its time generating data; disk sequential write could never exceed the generator's own 290 MB/s, whatever the disk. Data is now prepared before the clock starts. Bandwidth reports the best pass instead of the average, and the spread between passes is published so a noisy machine and a slow one no longer look alike.
- These metrics carry new names in reports (
memoryAccessLatencyNs,directRead4K_*,directWrite4K_*), so an old value and a new one can never be ranked side by side. - The lag filter discarded the lag. Every sample below 15 TPS was thrown away as "not representative". A server at 8 TPS is a server at 8 TPS; sustained lag is now measured, while genuine artefacts — catch-up ticks, GC pauses, a drifting sampler — are still excluded.
- The warm-up never protected a single measurement: it was reset right after
being set.
lightingUpdatemeasured its own preparation instead of the lighting work;tickingTileEntitylet part of its load stop ticking. - The standard benchmark measures the same thing everywhere. Three tests of
/bench startreadconfig.ymlwhile building their load, and so did the number of zones: an operator could reduce the load of a "standard" score without the report saying so. It now always runs the load every default installation has been measuring — 10 hopper lines of 50, 150 TNT, 2,000 chunks per zone, 8 zones — so default installations see no score change. The limits still apply to/bench test, custom mode and custom profiles. - Reports record the server's difficulty: Peaceful removes hostile mobs from the entity tests, so a comparison can see when two servers were not measuring the same thing.
/bench test stays out of the world you stand in
Most single tests ran in the launching player's world — usually the main world.
lightingUpdate dug shafts, chunkTicking cleared about 545 × 545 blocks,
explosion, hopper, redstone and others built and cleared their zones there,
mobSpawn removed every mob within 100 blocks of its zones, chunkLoading left
thousands of generated chunks in your world files, and playerWorldLoad left a
hole for every block it touched near spawn.
- Every test that writes to the world now runs in a dedicated world: the one
pinned with
/bench world set, otherwise a temporary flat world deleted at the end — even after a failure or/bench stop. When neither is possible, the test is refused instead of falling back to your world. - You are brought back where you were. Tests that need a player nearby take the player who launched them to their zone and bring them back to their position and game mode when the test ends, fails or is stopped — and someone who disconnects mid-test reconnects where they were.
playerWorldLoadrestores every block it changes to its original state, and never touches chests, spawners, signs or beds./bench start, custom profiles, stress-limit and tier runs are unchanged and skip no test.
Custom profiles run the load they ask for
The hopper limit meant for /bench test hopper <lines> silently capped every
profile: the bundled profiles asked for 20 to 100 lines and all ran 10. Profiles
now run what they ask, up to 500 lines per zone, and the bundled ones say 10 —
the load they have always run — so none of their results move.
- New bundled profile
hopper-heavy: 100 lines in each of 8 zones, ten times the standard load, for servers with large sorting systems. Heavy, and not comparable with/bench start. - Bundled profiles reach existing installations. A new bundled profile is
copied once; one you delete stays deleted; an untouched copy of an older
version is updated — including the
showcaseprofile that 1.8 always rejected — and a copy you edited is never overwritten.
Folia
Folia — and the forks built on it, such as Canvas — splits the world into regions, each ticked by its own thread, and refuses outright an operation on an entity from any other thread. Seven operations got that wrong.
- Auto-bench ran again.
/bench autobotvalidated its job over HTTP, then resumed on the global region, where putting the player into spectator threw on the spot: on Folia, auto-bench did not run at all. - Nobody is stranded in a benchmark world. A player reconnecting inside a leftover benchmark world is teleported out; that teleport failed on Folia before restoring the game mode, leaving the player in spectator with nothing able to get them out.
- Stress-limit runs restore the player's position and game mode,
/bench zones tpworks, startup cleanup of a previous run's leftovers actually runs, the scoreboard is reassigned when a player reconnects mid-run, and/bench zones cleansweeps each region on its own thread. - Gameplay tests that write to the world need a world pinned with
/bench world set, since Folia cannot create worlds while running; without one they are refused with a message that says how to pin one.
Permissions that hold, settings that apply
Most permissions in plugin.yml were declared but never checked: anyone with
voxelbench.use could start a benchmark or a stress-limit run, stop someone
else's run, or open the monitoring and report screens. They are now enforced on
the commands, in tab completion and on every menu screen.
- Every built-in test has its own node, and starting a test by its full name no
longer skips it.
/bench linkrequiresvoxelbench.link; teleporting to and clearing test zones requirevoxelbench.world; the settings screen requiresvoxelbench.settings. config.ymlcontained thebenchmark:section twice, and the second silently replaced the first; the file is repaired on upgrade, with a warning.dispersed-zones.defaultnow applies to custom mode; settings that nothing read were removed, and settings the plugin used were added with their defaults. New settings are always added to your file; nothing you set is overwritten.
Security, reports and integrations
- The embedded web dashboard is private by default. It listens on the server
machine only (
monitor.bind-address: "127.0.0.1"). Listening on any other address requires a dashboard password, set from the server console only — a command typed in game ends up inlatest.log. Passwords are stored with PBKDF2, and the login page follows the IP whitelist and turns an address away after five failed attempts in a minute. - LiteBans: only real sanctions. The hook read the commands players typed,
before any permission check: anyone typing
/ban <name> <text>produced a "player banned" alert, even when LiteBans refused the command. VoxelBench now listens to LiteBans' own events. On new installations LiteBans forwarding is off by default, and the typed reason is sent only if you enableremote-monitoring.events.litebans.include-reason. - Minecraft 1.17 submits reports again. With the default anonymization setting, preparing a report called a JSON method that 1.17 does not ship, so submission failed — and server verification, update checks and remote monitoring with it. The plugin is now built against the exact library version 1.17 ships.
- A benchmark is saved locally at the end of every run, whatever happens to the submission, with its status and the reason when it failed. With full anonymization, file paths inside JVM flags are masked too.
- No more invented numbers. Sub-scores the service never computes no longer
show as
0(the placeholders returnN/A), and scores without an upper bound are no longer drawn as bars out of 100.
Smaller fixes
Auto-bench: stopping a stress-limit or single-test job works, and jobs that cannot start fail at once with the real reason instead of waiting for a timeout. Folia: chunk preloading no longer defers every load by a tick, and Folia 1.20.x no longer logs a stack trace at every monitoring collection. Remote-monitoring requests go straight to the right address. Recovering after a crash no longer sweeps the main world when the run's temporary world is gone. The whole interface is translated — monitoring, custom profiles and report screens included — and a saved report reads in the viewer's language.
Upgrade
Drop the new jar in plugins/ and restart. config.yml is repaired and
migrated on first start; new settings are added, and nothing you set is
overwritten.
- Comparability (against 1.8.0): memory and disk results change scale and
names,
lightingUpdateandtickingTileEntitynow measure the load they announce, and servers that lag under load report that lag. The standard gameplay load is unchanged. Single gameplay tests launched with/bench testnow run in a flat benchmark world: they match what/bench startmeasures, but not your earlier/bench testruns. - Permissions: operators and holders of
voxelbench.*are unaffected. A group that only hadvoxelbench.useloses the guarded commands until it is given the corresponding nodes.voxelbench.test.mobpathfinding,.redstoneand.blockphysicsmoved from the single-core group to the gameplay group, their actual category. - Configuration: if a warning mentions the duplicated
benchmark:section, set again any value you had placed in its first copy. Monitoring events now use the thresholds yourconfig.ymlalways displayed: TPS drop after 10 s, TPS critical at 10.0, GC major at 200 ms of major pauses. - Web dashboard: if your
monitor.bind-addressis0.0.0.0(the former default) and no dashboard password is set, the dashboard no longer starts. Set a password from the console (/bench monitor auth password <password>) or bind it to127.0.0.1. - Remote monitoring: if an earlier version switched it off after the site
refused it, turn it back on with
/bench monitor remote on. Asend-intervalstill at the former default of 300 seconds becomes 60; acollect-intervalbelow 30 seconds is raised to 30. - LiteBans: an existing installation keeps forwarding sanctions if it did, but
without the typed reason until
remote-monitoring.events.litebans.include-reasonistrue. - Custom profiles: a profile of yours whose hopper step asks for more than the command limit now runs that many lines — its earlier results used fewer. VoxelBench names each such profile in the console at startup.
/bench test: a gameplay test launched from your main world now creates a temporary world for itself. To avoid that, or on Folia, pin a world loaded at startup with/bench world set— the test builds and clears its zones there. Auto-bench jobs that run a single gameplay test follow the same rule.- Profiler and memory inspection: operators can use them right away; the ring
buffer stays off until you turn it on. Public links and uploads can be turned
off with
profiling.share.enabled/profiling.upload.enabledandmemory-inspection.share.enabled/upload.enabled;memory-inspection.analysis.max-heap-mb(4096) andmemory-margin-mb(512) bound the memory an analysis may take. On a hosting panel, setmemory-inspection.host-disk-limit-gbto your plan's disk limit. If your backups or shared folders includeplugins/VoxelBench/, leave outheapdumps/.
Java 16 or newer for Minecraft 1.17 to 1.21.x; Java 25 for Minecraft 26.1 and later. Tested on Spigot, Paper and Folia, up to Minecraft 26.1.2.