dev branch
View on GitLab ↗
Minecraft JVM memory-retention diagnostics.
LeakWatch monitors Minecraft's heap and garbage-collection behavior, tracks post-GC memory, records lifecycle events, and helps identify persistent memory retention in heavily modded environments.
It is designed to answer a more useful question than "How much RAM is Minecraft using?":
What memory is actually staying alive, and does it keep growing?
🚧 Development Status
LeakWatch is currently in active development.
The core monitoring engine, GC analysis, lifecycle tracking, snapshots, diagnostics, in-game HUD, and MariesLib Command Center are already implemented and being validated in real modded Minecraft environments.
Development phases
- Live dashboard: heap, GC activity, post-GC live set, allocation rate, graph
- GC analysis: persistent-growth detection and retention trends
- Snapshots & comparison: capture and compare memory states
- JFR profiling: optional JVM allocation profiling
- Heap analysis: heap dumps and retention paths
- Lifecycle testing: world unload → GC → instance tracking
- Integrated vs dedicated comparison: identify behavior specific to shared JVM environments
LeakWatch does not replace Java's garbage collector. It is a diagnostic and investigation tool.
🖥️ In-Game Command Center
LeakWatch integrates with MariesLib's Command Center, providing a graphical interface for monitoring and diagnostics without having to remember commands.
Open the Command Center using MariesLib's Open Command Center key.
The key is unbound by default and can be assigned under Options → Controls.
Select LeakWatch from the Command Center.
Command Center
The LeakWatch category provides:
- 📊 Live status
- 📈 Live view
- 🧪 Leak test
- 📸 Snapshot
- 📤 Export
- 📋 Dump all
- 🔄 Compare
- ♻️ Reset baseline
The status card provides an at-a-glance view of the currently monitored JVM.
📊 LeakWatch Window
The full LeakWatch window provides separate Client and Server views.
Live monitoring
The window displays:
- Current heap usage
- Maximum heap
- Post-GC live set
- Allocation rate
- GC count and timing
- Current retention verdict
- 120-second memory graph
- Post-GC baseline
- Diagnostic output
The interface also identifies exactly which JVM is being displayed:
Client JVM
Dedicated Server JVM
Integrated Server — Shared JVM
This distinction is particularly important in heavily modded environments.
🔬 Client & Server Diagnostics
LeakWatch can monitor both sides of a multiplayer environment.
Client
The Client tab monitors the JVM running Minecraft's client.
Dedicated server
The Server tab requests diagnostic information from the dedicated server through LeakWatch's request channel.
The server only responds to:
- Operators with permission level 2+
- The host of an integrated server
This follows the same permission rules as /leakwatch.
Server-side files remain on the server. Diagnostic text requested through the Command Center is returned to the client.
For headless dedicated-server consoles, the one chat command, /leakwatch, remains available.
🧠 Why Post-GC Memory Matters
Raw heap usage alone doesn't tell you whether Minecraft is leaking memory.
A heavily modded instance can temporarily allocate enormous amounts of memory and then clean it up during garbage collection.
LeakWatch therefore tracks the post-GC live set.
For example:
Heap usage
10 GB ─────╮ ╭────╮
╰────╯ ╰─────
Post-GC
7 GB ────────╮
╰── 7.1 GB
A rising post-GC baseline is much more interesting than a temporary allocation spike.
LeakWatch uses this information to distinguish normal allocation activity from persistent growth.
🧪 Diagnostics
LeakWatch provides several diagnostic actions.
Leak Test
Leak Test checks expected Minecraft instance counts and compares them against objects currently reachable by the game.
For example:
ServerLevel: expected 30, reachable 30
ClientLevel: expected 1, reachable 1
MinecraftServer: expected 1, reachable 1
IntegratedServer: expected 1, reachable 1
Dimension keys are also reported by namespace:
expected dimensions:
minecraft: 3
stellaris: 9
allthemodium: 3
This is useful for identifying obvious lifecycle retention such as duplicate world instances.
Leak Test does not assume that an unexpected object count is automatically a memory leak. It reports the observed state so it can be correlated with the other diagnostics.
Repeatable Reload Test
The reload test extends Leak Test into a repeatable lifecycle experiment:
/leakwatch leaktest reload [cycles]
Instead of relying on a single before/after measurement, LeakWatch performs the selected lifecycle operation multiple times and records the post-GC live set after each cycle.
For example:
Reload 0 7.80 GB
Reload 1 8.23 GB
Reload 2 8.66 GB
Reload 3 9.09 GB
Growth: +430 MB / reload
LeakWatch fits the observed measurements across the reload cycles rather than treating one increase as proof of a leak.
The test can also incorporate class-histogram data when available, allowing persistent growth such as:
ItemStack
+1,290,000 instances
SomeChunkData
+39,000 instances
to be identified across repeated reloads.
Repeated growth is evidence of persistent memory growth associated with the tested lifecycle, not proof of which mod is responsible.
Force Clear
Force Clear is a diagnostic operation for determining how much memory can actually be released by a normal lifecycle teardown:
/leakwatch leaktest clear
It performs the supported teardown operation, allows lifecycle cleanup to complete, requests garbage collection, and measures the resulting post-GC live set.
For example:
Before 10.12 GB
After 6.84 GB
Released 3.28 GB
Force Clear is not a RAM cleaner.
It does not forcibly delete arbitrary mod state, clear unknown caches, or manipulate third-party mod internals. Memory that remains after the operation is still reachable or otherwise retained by the JVM and is not automatically considered a memory leak.
The purpose is to establish a useful diagnostic baseline:
How much memory did the game actually release, and how much remained alive afterward?
Regrowth Monitoring
A successful clear is not only about whether the heap number goes down.
LeakWatch can monitor the post-clear live set to determine whether the released memory remains released or begins growing again.
For example:
Force Clear
10.12 GB → 6.84 GB
5 minutes
6.91 GB
10 minutes
6.98 GB
indicates that the live set remained relatively stable during the observation period.
By contrast:
Force Clear
10.12 GB → 6.84 GB
5 minutes
8.10 GB
10 minutes
9.40 GB
indicates that memory was recovered but subsequently regrew.
LeakWatch reports this as regrowth, not as automatic proof of a leak.
This distinction makes Force Clear useful for investigating whether a lifecycle operation actually resolves the observed memory pressure or merely provides temporary relief.
Snapshot
Captures the current diagnostic state for later comparison.
Snapshots can include:
- Heap and post-GC measurements
- GC information
- Allocation rate
- Lifecycle state
- Class histogram data
- Per-owner shallow-memory totals
- Top classes by instance count and shallow size
Compare
Compares two snapshots to identify changes between them.
This is particularly useful when testing:
- World loading
- World unloading
- Dimension travel
- Resource reloads
- Player join/leave
- Server restarts
- Repeated lifecycle operations
Compare reports observed changes rather than assigning causation.
For example:
ItemStack
Instances: +1,290,000
SomeMod
Shallow size: +91 MB
A class or owner increasing across a lifecycle boundary is evidence worth investigating, not proof that it is leaking.
Dump All
Creates a complete diagnostic dump containing:
- Current status
- Recent GC events
- Memory trend
- Lifecycle events
- Leak-test results
- Histogram information
- Diagnostic measurements
Dump All is intended to provide a single artifact that can be attached to a bug report or used for later investigation.
📁 Output Files
LeakWatch stores diagnostic output under:
<gamedir>/config/leakwatch/logs/
Directory structure
config/
└── leakwatch/
└── logs/
├── exports/
├── snapshots/
├── reports/
├── session-*.log
└── dumpall-*.txt
Exports
exports/
Contains JSON exports created with:
/leakwatch export [name]
Snapshots
snapshots/
Contains JSON snapshots created with:
/leakwatch snapshot [name]
Snapshots are used by the comparison system.
Reports
reports/
Reserved for future diagnostic reports.
Session logs
Every action initiated through:
- The server-side
/leakwatchcommand - The Command Center
- Server request channels
is recorded with:
- Timestamp
- JVM side
- Action name
Example:
session-20260920-234533-server.log
Command output logging can be disabled with:
dumpCommandOutput = false
in:
config/leakwatch-common.toml
The Command Center's normal polling is not written to the session log.
🛡️ Safe File Handling
LeakWatch sanitises user-provided snapshot, export, and dump names.
Allowed characters are:
letters
digits
.
_
-
Names cannot escape the LeakWatch output directory.
Existing snapshots and exports are never overwritten.
Files created by older builds under:
<gamedir>/leakwatch/
are intentionally not migrated or read.
🧰 Commands
The graphical Command Center is the primary in-game interface, but commands remain available.
/leakwatch
/leakwatch status
/leakwatch gc
/leakwatch trend
/leakwatch reset
/leakwatch snapshot
/leakwatch export
/leakwatch dumpall
/leakwatch leaktest
/leakwatch histogram
/leakwatch compare
/leakwatch leaktest
/leakwatch leaktest reload [cycles]
/leakwatch leaktest clear
Class histogram by mod
histogram (also the Histogram button on the Client and Server tabs) lists the largest classes and a table of
owners, read in-process from the JVM's gcClassHistogram diagnostic command: no heap dump, no attach.
- The default run does a full GC and walks the whole heap, so the game can pause for a second or more on a large heap. It shows the same kind of warning as Snapshot, and Snapshot and Dump all now include a histogram (one more full GC each).
- Sizes are SHALLOW (the objects themselves, not what they reference), not retained size. Owners are guessed from
class package names against a mod-id to package table built from the loaded mods; there are also
minecraft,neoforge,jdkandunattributed(libraries land there). Arrays of any type are attributed tojdk. - Every histogram output starts with an Owner lookup line (mods resolved, packages, conflicted packages, multi-mod
jars, and the share of non-array shallow bytes that stayed
unattributed). Over 40% unattributed prints a warning that the mod-package lookup may be incomplete and the owner table is not reliable. Mods that share one jar are reported together asfile.jar (N mods: a, b, ...); a package claimed by two different jars is left unattributed. - On a server, histogram/snapshot/dump all/leak test run as background jobs: the window shows "running" and polls, and the server thread is never held waiting for the GC.
- Snapshots store owner totals for every class and the 300 largest classes. Compare then shows the per-owner change (exact) and the per-class instance-count change; a rise in a class's count across a world reload is evidence to look at, not proof.
/leakwatch is a thin wrapper over the same action layer the window uses, with the same permission rule (op level 2, or
the host of an integrated server). It is server side only: there are no client-side chat commands. The client JVM is
inspected through the Client tab of the LeakWatch window and the live window (/lwclient was removed).
The command exists for headless dedicated-server consoles, where there is no window.
🖥️ Live window
The live window is a small always-on window (a HUD, no input during play) with LeakWatch's key numbers. It is separate from the command-center window, which stays for actions (leak test, snapshot, export, compare, histogram).
Press:
F8
to show or hide it, or use the Pin live window card in the LeakWatch category of the command center.
It shows, in order (compact mode shows the first group only):
- The retention verdict (colour-coded, hedged wording), which JVM it shows (client, client + integrated server sharing a
JVM, integrated server, dedicated server) and, while the baseline is being learned,
re-learning baseline (n/6 points, m/5 min). - Heap used/max, the live set (after the last full GC, and how long ago), headroom (max minus live set), the live set as a share of the max heap, and a memory pressure reading. Pressure is separate from the leak verdict: a pack with no leak can still be short of memory (a live set at 84% of max reads as tight, not fine). Comfortable below 60%, moderate from 60%, tight from 75%, critical from 90% of the max heap; GC using 10% or more of wall time counts as tight too.
- The used-heap graph with the post-GC baseline and last forced GC lines.
- Allocation rate, GC count, GC time as a share of the last 60 s, and the mean young-GC interval.
- The last three lifecycle markers and the last leak test result in one line (e.g.
levels 30/30, no excess, 4m ago).
Source. Client reads this game locally. Server asks the server every 2 s over the LeakWatch channel, only while the
Server source is selected and the window is visible (never when hidden); it needs the same permission as /leakwatch
(op level 2, or the host of a single-player world) and says "No access" or "The server does not have LeakWatch" when it
cannot show anything.
Moving and resizing. Use the Edit live window card, the Edit live window layout key (unbound by default), or your
"edit all HUDs" key from another Marie mod (the window joins MariesLib's shared edit mode). In edit mode drag the window,
drag its edges or corners, click Source or Mode (compact/expanded) above it, and use the mouse wheel over it for
opacity. Position and size are kept per mode in config/leakwatch-ui-state.json; visibility, opacity, source and mode in
config/leakwatch-client.toml. Position and size are clamped to the screen, so a GUI scale or window size change never
leaves the window off screen or unreadable. Rows that do not fit are dropped from the bottom instead of being clipped.
The window reads the monitoring engine once per second (not per frame), keeps the formatted text, and only draws it. It never forces a GC or runs a histogram, leak test or snapshot; those stay explicit actions.
🔐 Heap Dumps
Heap dumps are planned for a later development phase.
They will not be stored under config/.
Heap dumps can contain sensitive information, including session-related data, and Minecraft configuration directories may be included in modpack exports.
Future heap dumps will instead be stored outside the configuration directory, for example:
<gamedir>/leakwatch-dumps/
Heap-dump analysis will be introduced separately from the lightweight monitoring system.
🧩 MariesLib
LeakWatch requires MariesLib 0.1.3-beta or newer wherever LeakWatch itself is installed (client, server, or both).
Networking: optional on either end
The Server tab talks to the server over a MariesLib request channel, registered as optional
(MarieRequestChannel#optional(), which uses NeoForge's PayloadRegistrar#optional(); verified against NeoForge 21.1.229).
During the connection handshake NeoForge drops an optional channel that the other side does not have, so:
- a client with LeakWatch can join a server without it, and a server with LeakWatch accepts a client without it;
- a vanilla client is not refused because of LeakWatch (any other mod with a required channel can still refuse it);
- the Server tab then shows "Server does not have LeakWatch" instead of trying to send;
- if both ends have the channel, its protocol version must still match, or the connection is refused.
The dependency on MariesLib in neoforge.mods.toml stays side = "BOTH": LeakWatch's common code references MariesLib, so
MariesLib must load wherever LeakWatch does. Dependency sides are checked locally and do not make the remote end require
anything; only payload negotiation does.
Game state is read on the game thread
Background jobs (the automatic leak check, leak test, snapshot, histogram, dump all) never touch a level, server or player. The live level counts are read on the server thread / client thread and only plain numbers and strings come back. If the game thread does not answer within a second, the automatic check is skipped, and a manual leak test says its expected counts may be incomplete, instead of reporting missing counts as stale instances.
Startup self-check
At startup LeakWatch round-trips one small instance of each record type it saves through the same Gson the exporter uses. If that fails it logs one error naming the failing type and stating that export, snapshot and compare are affected; it does not crash and does not change any file format.
The project uses MariesLib for its reusable UI and Command Center infrastructure.
When developing LeakWatch locally, a checkout located beside the project:
../Marieslib
is used as a composite Gradle build.
Without a local MariesLib checkout, the dependency is resolved from GitHub Packages using:
gpr.user
gpr.key
or:
GITHUB_ACTOR
GITHUB_TOKEN
⚠️ Diagnostic Philosophy
LeakWatch is intentionally conservative.
A high allocation rate does not automatically mean a memory leak.
Likewise, high heap usage does not automatically mean something is wrong.
LeakWatch therefore reports observations such as:
- Persistent growth
- Post-GC live-set increases
- Instance-count changes
- Lifecycle correlations
- Allocation activity
- Potential retention
rather than automatically declaring a specific mod guilty.
Future attribution features will distinguish between observed data, likely associations, and stronger evidence.
Development
LeakWatch is being developed for NeoForge 1.21.1 and is currently focused on building reliable JVM diagnostics before introducing deeper heap analysis and retention-path tooling.
The goal is simple:
Don't just show how much memory Minecraft is using. Help explain why it stays there.