Changelog · Plugin

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…

Wby Wasab_II
September 25, 2026
2

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 in libraries: and plugins declared with paper-plugin.yml count for that plugin.
  • A ring buffer that catches lag by itself. /bench profile ring on keeps 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 summary counts 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 with all, which skips the garbage collection but counts garbage too), and saves each summary next to your reports, in reports/memory/.
  • /bench memory dump writes 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. Add gzip to compress it afterwards, with the server running.
  • A dump that would get the server stopped is refused. Past the watchdog limit (settings.timeout-time in spigot.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 add force if the server may stop — the preview then repeats the warning in red, and force lifts 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 analyze reads the dump for you — or add analyze to the dump command to do both in one go. A summary counts a plugin's cached byte[] 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: false turns 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 -Xmx would. Set memory-inspection.host-disk-limit-gb to 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, preview and 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 it voxelbench.memory.dump for dumps and their analysis, voxelbench.memory.share and voxelbench.memory.upload to 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, dump and analyze open 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 and config.yml allow 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 confirm confirms your last preview — a dump, or a profile or report preview — from the console too, with all the checks of the full command. Players also get a Yes/No window with the preview (confirmation.popup: false turns 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 (permission voxelbench.monitor.web). on lists 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. status shows 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 reload applies its settings, and the plugin never rewrites config.yml itself.
  • 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 /tps shows 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.events settings, 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/metrics gains a performance section 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, _5m or _15m suffix), %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. lightingUpdate measured its own preparation instead of the lighting work; tickingTileEntity let part of its load stop ticking.
  • The standard benchmark measures the same thing everywhere. Three tests of /bench start read config.yml while 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.
  • playerWorldLoad restores 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 showcase profile 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 autobot validated 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 tp works, startup cleanup of a previous run's leftovers actually runs, the scoreboard is reassigned when a player reconnects mid-run, and /bench zones clean sweeps 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 link requires voxelbench.link; teleporting to and clearing test zones require voxelbench.world; the settings screen requires voxelbench.settings.
  • config.yml contained the benchmark: section twice, and the second silently replaced the first; the file is repaired on upgrade, with a warning.
  • dispersed-zones.default now 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 in latest.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 enable remote-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 return N/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, lightingUpdate and tickingTileEntity now 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 test now run in a flat benchmark world: they match what /bench start measures, but not your earlier /bench test runs.
  • Permissions: operators and holders of voxelbench.* are unaffected. A group that only had voxelbench.use loses the guarded commands until it is given the corresponding nodes. voxelbench.test.mobpathfinding, .redstone and .blockphysics moved 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 your config.yml always 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-address is 0.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 to 127.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. A send-interval still at the former default of 300 seconds becomes 60; a collect-interval below 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-reason is true.
  • 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.enabled and memory-inspection.share.enabled / upload.enabled; memory-inspection.analysis.max-heap-mb (4096) and memory-margin-mb (512) bound the memory an analysis may take. On a hosting panel, set memory-inspection.host-disk-limit-gb to your plan's disk limit. If your backups or shared folders include plugins/VoxelBench/, leave out heapdumps/.

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.

How did you find this article?
Sign in to react