What is deepheap?
deepheap is an MCP server that lets an AI client analyze Java (and other JVM languages) heap dumps, thread dumps, and GC logs via tool calls. You point your client at the deepheap binary, ask the client to load an artifact, and then ask questions in natural language: "what's holding the most memory?", "which threads are blocked?", "are GC pauses growing?", "where are the duplicate strings?"
A headless replacement for Eclipse MAT and VisualVM: no GUI, the AI client is the interface.
You need:
- A JVM artifact to analyze: a HotSpot HPROF heap dump (from
jmap,jcmd GC.heap_dump, or-XX:+HeapDumpOnOutOfMemoryError), a thread dump (jstack / JEP 425 JSON), or a GC log. - An MCP-capable AI client (Claude Code, Cursor, VS Code with the MCP extension, etc.)
- JDK 25+ on the analyzer machine (see the FAQ)
- A deepheap license file (required for heap-dump analysis; optional for thread-dump and GC log tools). See Downloads.
A typical session: connect → load dump → ask top-level questions → drill into specific objects or retention paths. Skip straight to First analysis if you'd rather see a worked example.
Why deepheap?
Where existing tools fall short for AI-augmented workflows, and how deepheap addresses the gaps.
The problem with Eclipse MAT and VisualVM
They excel at interactive GUI analysis but hit friction:
- GUI-oriented: headless batch reports exist (MAT's
ParseHeapDump.sh) but interactive query lives in the desktop UI, and outputs target humans, not machines. - Memory-hungry on large dumps: when the in-memory object graph approaches or exceeds available RAM, parse time and stability degrade; tuning the analyzer's own heap is often required.
- Query language barrier: OQL and similar query languages are powerful but require knowledge of query syntax and HPROF internals to be useful.
- Separate context: findings live in a separate GUI window; copying relevant data to an AI conversation requires manual effort.
Why MCP over OQL?
OQL is powerful but requires the analyst to know what they're looking for. An MCP tool call lets the AI iterate: start with top-level class counts, narrow to suspect instances, trace a GC root path, without the analyst switching context or learning a query language. The AI drives the investigation; deepheap provides the facts.
Why deterministic engine + LLM interpretation?
deepheap does not feed raw heap bytes to the LLM. It computes retained sizes, resolves reference chains, and extracts structured facts using deterministic algorithms. The LLM reads those facts and synthesizes an explanation; it cannot invent object counts or retained sizes, because it does not see the raw dump. This matters especially for enterprise heap dumps that may contain user data, secrets, or prompt-injection payloads. (String content is redacted by default; use --show-strings to override when you control the dump.)
How it works
How data flows from a heap dump on disk to the AI client, and the two filters that sit in between.
Data flow
The dump never leaves your machine. The MCP client talks to a local deepheap server that queries the dump and returns structured results:
- Local execution: the deepheap process runs entirely on your machine. No outgoing network calls, no telemetry, no upload of the dump. Your MCP client still sends prompts and tool results to its model provider over the network; see the FAQ for the practical implications.
- Structured results: tool calls return class counts, retained sizes, GC root paths, and field values as deterministic facts. The AI synthesizes an answer from those facts.
- Filtered content: raw heap strings, byte[], char[], and short[] are summarized before they reach the AI client (see the two gates below).
String-content gate
On by default. Every String, StringBuilder, StringBuffer, and Groovy GString value is replaced with a shape summary like String(length=42, format=json-object). Every byte[], char[], and short[], regardless of whether it backs a String, is redacted to byte[N] (REDACTED, format=uri). The format label is chosen by the server from a closed vocabulary of about 20 values (json-object, json-array, uri, jwt, pem, base64, xml, sql, uuid, iso-date, …). Because that label is only about four bits drawn from a fixed set, heap content can't carry natural-language instructions to the model no matter how much attacker-controlled text the underlying string holds. Disable with --show-strings only when you fully control and trust the dump.
Sensitive-name gate
On by default. A second pass over rendered object detail redacts field or map-key values whose name matches a credential pattern (password, token, secret, apiKey, credential, …). Disable with --show-secrets. The two gates are independent; you can enable one without the other.
Key concepts
Terms used on this page. Skim once, refer back as needed.
- MCP
- Model Context Protocol. A standard that lets AI clients call tools exposed by a local or remote server.
- HPROF heap dump
- A binary snapshot of every object in a JVM's heap, plus thread stacks and class metadata. File extension
.hprof. - Shallow size
- The memory used by an object itself: its header and fields, nothing more.
- Retained size
- The memory that would be freed if this object were garbage-collected: itself plus everything it exclusively keeps alive.
- Dominator tree
- X dominates Y if every path from a GC root to Y goes through X. The tree of all such relationships shows who really retains what.
- GC root
- An entry point the garbage collector starts from: static fields, thread stacks, JNI references, locked monitors.
- ClassLoader leak
- A discarded ClassLoader is still reachable, keeping all its classes and their static state alive. Common in OSGi, plugin systems, and hot-redeploy app servers.
- Off-heap memory
- Memory allocated outside the Java heap: DirectByteBuffer, native memory segments, mapped files. Counts against process RSS but not against the Java heap.
Quickstart
deepheap ships as a self-contained zip. No installer, no daemon. Your AI client launches the binary.
- JDK 25+ (Adoptium Temurin recommended)
- macOS, Linux, or Windows
- ~20 MB disk for the binary; heap dumps stay wherever they are
- A deepheap license file (required for heap-dump analysis; optional for thread-dump and GC log tools). Obtained from the Downloads page.
- 1
Request a license and download
Request a trial license and grab
deepheap-mcp-<version>.zipfrom the Downloads page. The license file is emailed to you and is required for the heap-dump tools; the thread-dump and GC log tools run without one. - 2
Unzip to a permanent location
# macOS / Linux unzip deepheap-mcp-<version>.zip -d ~/deepheap # Windows (PowerShell) Expand-Archive deepheap-mcp-<version>.zip -DestinationPath $HOME\deepheap
- 3
Verify the installation
# macOS / Linux ~/deepheap/deepheap-<version>/bin/mcp --help # Windows (PowerShell) & "$HOME\deepheap\deepheap-<version>\bin\mcp.bat" --help
Usage text should print.
- 4
Connect your MCP client
See MCP setup below for client-specific configuration.
- 5
Load a dump and ask a question
Restart your client so it picks up the new server, then jump to First analysis for a worked example.
Your first analysis
Once wired up, the AI does the work. You ask in natural language; it picks the right tools. A first session might look like this:
What you ask
- Load the heap dump at
/path/to/app.hprof. - Show me the top 20 classes by retained size.
- Why is this
com.example.SessionCachestill alive? - Find duplicate strings wasting the most memory.
- Are there any open file descriptors I should know about?
What runs under the hood
heap_dump__read) parses the file and builds the dominator tree. Expect seconds on a small dump, several minutes on a multi-gigabyte one. Every subsequent call is fast because the parsed snapshot is cached.MCP setup
deepheap speaks MCP over stdio (default) or Streamable HTTP. Pick your client:
Start with stdio mode: your client launches the server as a subprocess. Use HTTP for shared or remote deployments where multiple clients connect to one running server.
bin\mcp.bat instead of bin/mcp. When registering a stdio server, wrap it in cmd /c, since batch files are run by the shell rather than executed directly. For example: claude mcp add --transport stdio deepheap -- cmd /c C:\deepheap\deepheap-<version>\bin\mcp.bat. The examples below show macOS/Linux paths; substitute your Windows path.Add it with the CLI (recommended)
claude mcp add writes the config for you. Pick a scope:
# Just this project, just you (default) claude mcp add --transport stdio deepheap -- /path/to/deepheap/bin/mcp # All your projects, just you claude mcp add --transport stdio --scope user deepheap -- /path/to/deepheap/bin/mcp # Shared with your team (writes .mcp.json, commit it) claude mcp add --transport stdio --scope project deepheap -- /path/to/deepheap/bin/mcp
Anything after -- is the launch command, so pass server flags there, for example -- /path/to/deepheap/bin/mcp --root=/path/to/dumps. Confirm it registered with claude mcp list or claude mcp get deepheap, and check live status from inside a session with /mcp (which manages and authenticates servers but does not add them).
Or edit .mcp.json directly
The team scope writes this file in your project root; you can also create it by hand:
// .mcp.json { "mcpServers": { "deepheap": { "type": "stdio", "command": "/path/to/deepheap/bin/mcp" } } }
--root=/path/to/dumps (a launch argument) to confine reads to your dump directory.Common tasks
Task-oriented playbooks for the questions that come up most often. Open any card for the tool sequence and what to look for in the output. If you want a single end-to-end example first, see First analysis.
Start with the big picture, then narrow down to the leaking objects and trace who's keeping them alive.
- Top retained classes that aren't framework baselines (e.g.
char[],String). Application-specific caches, containers, or session objects high in this list are the first suspects. - A large gap between shallow and retained size on one class signals the entry point to a big subgraph.
- One or two outsized instances of the same class, not millions of small ones. A single retained map with 20 GB underneath beats a swarm of tiny objects.
- If the top of the list is uninformative, drill into the dominator tree node for the largest application object (see card 6).
Given an object ID (from heap_dump__get_instances or heap_dump__get_object_detail), trace the exact path from a GC root to that object.
java_frameroot: the object is on a live thread's stack. Likely held by an in-flight request, a long-running task, or a thread that should have exited.jni_globalroot: a native library is holding it. Common with JDBC drivers, JNI integrations, off-heap caches.sticky_classor static field: kept alive by a ClassLoader. If the ClassLoader itself looks orphaned, jump to card 3.- A long retention chain through a
HashMap,ConcurrentHashMap, orThreadLocalis usually the actual leak surface.
ClassLoader leaks are common in OSGi containers, application servers, and plugin systems. Each leaked loader prevents all its classes and their static state from being GC'd.
- Multiple copies of the same class loaded by different loaders is the telltale sign of a redeploy or plugin leak.
- ClassLoaders with surprisingly large retained sizes. The loader's retained size includes every class it loaded and all their static state.
- On the GC root path, look for the framework-level container (
WebAppContext, OSGiBundle, plugin manager) holding a reference it should have released. ThreadLocalvalues rooted in pooled threads are a frequent culprit. The thread outlives the redeploy and pins the old loader.
GC logs reveal pause-time and throughput trends; heap dumps reveal which objects are retained. Use both together to confirm a leak and identify the culprit.
- A rising live-data trend across consecutive Full GCs is a leak signature, not a sizing issue.
- Declining reclamation efficiency confirms the heap is filling with objects the collector can't reclaim.
- Outlier pauses or humongous-allocation events. Drill in with
gc_log__get_pause_outliers/gc_log__get_humongous_eventsbefore suspecting the heap dump. - When citing the result, name both surfaces: "GC log shows live data growing from X to Y MB; heap dump shows class Z retains W% of heap, rooted at S."
Applications that parse or intern large volumes of text often accumulate thousands of String instances with identical content, each backed by a separate char array.
- A handful of short, high-cardinality values (status codes, enum-like strings, package names) responsible for most of the waste.
- Long unique strings duplicated thousands of times are often parsed config, JSON keys, or DB column names.
- The owning class on inbound references tells you where to intern: a single producer (a parser, a row builder) is easy to fix; many producers means the value should be a flyweight.
- The fix is usually
String.intern(), a dedicated intern cache, or switching to enums / flyweights for high-frequency values.
Tool reference
The full set of MCP tools, grouped by artifact (heap dump, thread dump, GC log) and by function. Everything in this list is also invocable through natural-language prompts; you rarely need to name a tool directly. Paging tools validate their inputs: limit is capped at 10,000 per page (a few tools document a lower maximum, and a documented 0 = all still returns every row), offset is capped at 1,000,000, and out-of-range or fractional values are rejected with an error naming the bound. Each entry below includes an example output, captured from a small com.example demo application and abridged for readability; live output may include more rows and sections as releases evolve.
Heap dump
heap_dump__read keeps several snapshots in memory, keyed by file path. Per-object tools (get_object_detail, get_instances, get_gc_root_path, and similar) take an optional filePath to choose which loaded dump to act on; it is required once more than one dump is loaded, because object IDs are not unique across dumps. With more than one loaded, object IDs in their output are tagged 0x...@<label> with a short unique per-dump label (the file basename, or a longer path suffix when basenames collide). Pass the tagged id straight back and the label selects the source dump when filePath is omitted (an explicit filePath wins). The tag is applied at the structured id-emission sites, so it is content-safe for --show-strings responses too (a hex-looking class name, field name, or string value is never rewritten). Aggregate tools (get_classes, get_gc_roots, and similar) take an optional filePaths array: omit it (or pass one entry) to query a single dump; two or more entries select diff mode. get_classes renders the class-histogram diff (delta columns, unchanged rows hidden, generated names folded), and the other aggregate tools render added/removed/changed, count, or scalar diffs keyed to their natural identity (or, where that identity is redacted, a non-redacted field such as JMX resource class or agent type). Every aggregate tool now supports diff mode. Use heap_dump__list_snapshots to see what is loaded and heap_dump__close to free one.- heap_dump__read Parse an HPROF file and build the object graph, dominator tree, and retained sizes. Required first step. Takes seconds to minutes depending on dump size. Also reads gzip-compressed heap dumps (
.hprof.gz) written by the JDK's own dump tools, with no decompression to disk. The JDK writes these files as many small independent gzip blocks, so deepheap can jump straight to the objects it needs, decompressing about 1 MiB at a time into a bounded in-memory cache (64 MiB by default). The first analysis includes one streaming decompression pass to map the block layout; the map is cached, so reopening the same file skips it. Only multi-block gzip is read directly: a single-blockgzipfile is not seekable, so the error message says to decompress it first (the decompressed.hprofalways works) or re-dump as multi-block gzip viajcmd. Accepts an optionalmapping_pathto apply a ProGuard/R8 mapping and permanently deobfuscate class and field names on load; if that file cannot be read or parsed, the dump still loads without deobfuscation and the response explains how to retry viaheap_dump__reconfigure_snapshot. Only one dump is parsed at a time; a read issued while another load is in progress is rejected with a retry message. Several dumps can be held at once for diffing, but a load is refused when its projected off-heap cost plus the already-loaded dumps would exceed the off-heap budget (default is the smaller of 32 GB and half of physical RAM); the error lists the loaded snapshots so you can close one first.Example output
heap_dump__read(filePath="/data/dumps/demo.hprof")
Successfully parsed heap dump.
Layout
Setting Value layout HotSpot 25 (>= 15 layout) compressed_oops true compressed_class_pointers true compact_object_headers false object_header_size 12 bytes array_base_offset 16 bytes object_alignment 8 bytes dominator_exclude_refs none These settings are inferred from the object and pointer sizes in the dump records, not read from a header field (HPROF does not store them). If the inference is wrong because the source JVM ran with different flags, correct them with heap_dump__reconfigure_snapshot; shallow and retained sizes depend on these settings.
File
Field Value Path /data/dumps/demo.hprof Size 213,473,515 bytes HPROF timestamp 2026-06-07T15:11:43.955Z JVM
Field Value Architecture 64-bit Version OpenJDK Runtime Environment (build 25.0.2+10-LTS); OpenJDK 64-Bit Server VM (build 25.0.2+10-LTS, mixed mode) JDK build java.base@25.0.2 Platform macOS 15.7.4 · x86_64 VM started 2026-06-07T15:11:37.486Z JVM uptime 6s GC algorithm G1GC Compact strings true Integer cache high 127 Heap
Field Value Classes 2,610 Class loaders (defined ≥1 class) 3 GC roots (records) 2,502 Objects (total) 445,853 Objects bytes (total) 204,022,560 Objects (live) 419,643 Objects bytes (live) 55,050,512 Objects (garbage) 26,210 Objects bytes (garbage) 148,972,048 Loss internal padding (live) ~526,130 bytes Loss external alignment (live) ~393,837 bytes Live = reachable via all reference types. Retained/dominator excludes: none.
Compressed dump compatibility (.hprof.gz)
Which producers yield a readable
.hprof.gz. Only multi-block (multi-member) gzip is supported.Produced by Supported jcmd <pid> GC.heap_dump -gz=N(JDK 15 or later)Yes jmapwith the gz option (JDK 16 or later)Yes JDK 22+ parallel heap dumps with gz Yes Azul Zing / Platform Prime jcmd <pid> GC.heap_dump -gz=NYes bgzipYes gzip(command line)Not directly. Single-block output is not seekable; decompress it first ( gunzip) and open the.hprof, or re-dump withjcmd -gz=NpigzNot directly. Compresses in parallel but also emits single-block output; decompress it first and open the .hprofjhsdb jmap --binaryheapwith the gz optionNot directly. The Serviceability Agent writes single-block gzip; decompress it first, or dump uncompressed (uncompressed jhsdb dumps are supported) Uncompressed .hprofYes, unchanged - heap_dump__reconfigure_snapshot Change JVM layout settings (compressed OOPs, compact headers, per-level field layout, array base offset) and reanalyze without re-reading the file. Any parameter you omit keeps its current effective value, including the auto-detected Azul Zing/C4 corrections (8-byte instance header, 16-byte array base, per-level field packing), so changing one axis never silently reverts the others. Also accepts
mapping_pathto apply a ProGuard/R8 deobfuscation mapping if one was not supplied at load time. Omittingdominator_exclude_refs(or passing null) keeps the snapshot's current reference filter, so a layout-only or mapping-only reconfigure leaves it unchanged; pass[]to reset it to include all reference types. Use when: retained sizes look off because the snapshot's layout assumptions don't match the dump's source JVM, or when you have a ProGuard mapping you forgot to pass toheap_dump__read.Example output
heap_dump__reconfigure_snapshot(dominator_exclude_refs=["weak", "soft"])
Snapshot reconfigured and analysis complete.
Layout
Setting Value layout HotSpot 25 (>= 15 layout) compressed_oops true compressed_class_pointers true compact_object_headers false object_header_size 12 bytes array_base_offset 16 bytes object_alignment 8 bytes dominator_exclude_refs weak, soft Heap
Field Value Classes 2,610 Class loaders (defined ≥1 class) 3 GC roots (records) 2,502 Objects (total) 445,853 Objects bytes (total) 204,022,560 Objects (live) 419,643 Objects bytes (live) 55,050,512 Objects (garbage) 26,210 Objects bytes (garbage) 148,972,048 Loss internal padding (live) ~526,130 bytes Loss external alignment (live) ~393,837 bytes Live = reachable via all reference types (mask = 0). Retained/dominator excludes: weak, soft.
(File and JVM sections unchanged from heap_dump__read; output abridged)
- heap_dump__list_snapshots List every heap dump currently held in memory, with file size, estimated off-heap RAM cost, load time, JVM layout, and dominator filter. The default snapshot (used when a tool call omits a file path) is marked. The server keeps multiple dumps loaded at once, so this is how you see what is resident. Use when: you have loaded more than one dump and want to check RAM usage or confirm which one tools will act on by default.
Example output
heap_dump__list_snapshots()
Loaded heap dumps (2)
filePath size est. off-heap layout /data/dumps/before.hprof 1.2 GB ~1.2 GB HotSpot 25 (>= 15 layout) * /data/dumps/after.hprof 1.4 GB ~1.4 GB HotSpot 25 (>= 15 layout) * marks the default snapshot. Estimated off-heap is a rough multiple of file size; mmap pages are reclaimable and not counted toward the budget.
- heap_dump__close Free the off-heap memory of one loaded dump. This is the only way to reclaim that RAM short of restarting the server. filePath is resolved against the loaded snapshots, not the filesystem: the absolute registry key shown by
heap_dump__list_snapshots, a relative path to the same file, or a unique basename all select the snapshot, so a deleted-but-loaded dump still closes by the key or basenameheap_dump__list_snapshotsshows. Use when: you are done with an older dump and want its memory back before loading a new large one.Example output
heap_dump__close(filePath="/data/dumps/before.hprof")
Closed snapshot: /data/dumps/before.hprof
1 snapshot still loaded; default is now /data/dumps/after.hprof.
- heap_dump__get_summary The baseline summary of an already-loaded dump: the same Layout, heap, object/class/GC-root, JVM, and JDK sections that
heap_dump__readreturns, rendered without re-parsing. Handy for re-orienting on a dump loaded earlier, or for picking one of several loaded dumps withfilePaths. A single-dump summary also reports Valhalla flattening (the JEP 401 value-classes implementation; other Valhalla features are out of scope) whenever an eight-byte-ID dump is not plainly free of the pattern: Layout shows the detected HotSpot generated-ID convention, or an UNSUPPORTED_OR_AMBIGUOUS status with its reason, while Heap shows total, live, and garbage flattened-record subsets after successful corroboration. Flattening is inferred from generated, unaligned HPROF IDs; HPROF contains no direct physical-layout flag. Four-byte-ID dumps predate the pattern and get no report. Use when: you want the headline numbers for a loaded dump again without paying another parse. Passing two or morefilePathsrenders a scalar diff (the orientation call for comparing dumps).Example output
heap_dump__get_summary(filePaths=["/data/dumps/demo.hprof"])
Identical to the heap_dump__read summary (Layout / File / JVM / Heap sections); output omitted here for brevity.
Example output (diff of two dumps)
heap_dump__get_summary(filePaths=["/data/dumps/before.hprof", "/data/dumps/after.hprof"])
Summary diff (2 snapshots): before.hprof, after.hprof
metric before.hprof after.hprof Δ Heap size (live) 1.1 MB 1.2 MB +107.5 KB Live objects 27,953 32,217 +4,264 Classes 899 900 +1 GC roots 811 810 -1 JVM vendor Azul Systems, Inc. Azul Systems, Inc. A JVM vendor/version mismatch between the dumps would be flagged above the table. Output abridged.
- heap_dump__get_classes All classes ranked by instance count, shallow size, retained size, or static retained size. Each row renders as className @ 0xHEX so the class object id is available for follow-up calls (handy for inspecting static fields). Each row also shows a Static Retained column (the portion of retained size held by the class object's own static fields and the singletons they keep alive) and retained as a 2-decimal percentage of the live heap. Sort by static_retained to surface static-only leaks that have zero instances but high retention. Supports name filters, regex, and package grouping. On a dump where the recognized Valhalla value-flattening pattern (the JEP 401 value-classes implementation; other Valhalla features are out of scope) is detected, flattening="flattened" keeps only classes with flattened value instances and sortBy="flattened" ranks by that count; either option adds a Flattened column matching the row's scope, so the ordinary aligned population is the instance count minus that column. Both options are rejected on any other dump and in diff mode. When only some of the diffed dumps have flattening detected, the diff header carries a note, because those dumps' instance counts include flattened value records.
Example output
heap_dump__get_classes(sortBy="retained", limit=8)
Classes by total retained size (live):
# Class Instances Shallow (B) Retained (B) Static Retained (B) % Live Internal padding (B) External alignment (B) 1 java.lang.Object[] @ 0x7eb8559c8 3,350 174,976 53,062,456 0 96.39% 0 7,904 2 java.util.ArrayList @ 0x7eb8669a0 1,013 24,312 52,788,312 360 95.89% 0 0 3 byte[] @ 0x7eb8453a0 163,472 46,647,808 46,647,808 0 84.74% 0 370,256 4 java.lang.Class @ 0x7eeab7830 2,501 33,744 34,568,248 0 62.79% 0 5,489 5 java.util.HashMap @ 0x7eb8662a0 428 20,544 23,307,792 272 42.34% 1,712 0 6 java.util.HashMap$Node[] @ 0x7e0fe5bb8 412 573,184 23,300,128 0 42.32% 0 0 7 jdk.internal.loader.ClassLoaders$AppClassLoader @ 0x7e0dcbfa8 1 96 22,939,480 32 41.67% 3 0 8 com.example.demo.OrderCache @ 0x7eb8dacd8 0 0 22,923,560 22,923,560 41.64% 0 0 … 2,156 more rows not shown (total shallow: 7.2 MB, total retained: 79.4 MB)
Example output (diff across three dumps)
heap_dump__get_classes(filePaths=["/data/dumps/t1.hprof", "/data/dumps/t2.hprof", "/data/dumps/t3.hprof"], sortBy="count")
Class diff (3 snapshots): t1.hprof, t2.hprof, t3.hprof
Generated class names (lambdas, CGLIB proxies) are folded; pass normalizeGenerated=false to inspect individual sites.
# Class t1.hprof t2.hprof t3.hprof Δcount Δretained 1 java.lang.String 8,555 9,553 8,953 +398 +18.7 KB 2 java.util.HashMap$Node 1,079 2,076 1,476 +397 +35.3 KB 3 com.example.demo.SessionEntry 100 500 200 +100 +1.6 KB 4 + com.example.demo.PendingRequest 0 25 25 +25 +400 B 5 - com.example.demo.WarmupTask 30 0 0 -30 -480 B A column per snapshot, with the first→last Δ. The per-snapshot series is the payoff of multi-dump diff:
SessionEntryspiked to 500 then fell back to 200, so its +100 net delta alone would hide the peak.+added,-removed; unchanged classes hidden unless includeUnchanged=true. Sorting is two-axis:sortBypicks the metric (count or retained) anddiffRankthe statistic over the series. The defaultdiffRank=deltaputs the biggest growers first;diffRank=valueranks by the latest dump's footprint (so a large class that barely changed stays on top), anddiffRank=churnby the biggest absolute move, either direction. Output abridged. - heap_dump__get_class_hierarchy Show the superclass chain and subclasses for any class, with instance count and retained size per level. Every level renders as className @ 0xHEX so the class object id is available for drill-downs (get_instances, get_object_detail, or another get_class_hierarchy call). Pass useRegex=true with anchors for exact-name match.
Example output
heap_dump__get_class_hierarchy(className="com.example.demo.Event")
## Class hierarchy: com.example.demo.Event (classObjectId=0x7eb8dad58) Resolved from: class name 'com.example.demo.Event' java.lang.Object @ 0x7eb8c0090 [296 inst / 4,928 B retained] └── com.example.demo.Event @ 0x7eb8dad58 [0 inst / 0 B retained] ← self ├── com.example.demo.PaymentEvent @ 0x7eb97c4d0 [200 inst / 8,000 B shallow / 8,000 B retained] └── com.example.demo.OrderEvent @ 0x7eb97b9f8 [200 inst / 6,400 B shallow / 6,400 B retained]
- heap_dump__get_instances Find instances of a class (substring or regex match), sorted by retained, shallow, or random. Supports field value filters and subclass inclusion. Omit the class name for heap-wide top-N. The response is prefixed with a Resolved classes line listing every class the name fragment matched, each with its class object id, so you can drill into static-field retention without a second lookup. Pass a list of field names (or "*" for all) to project them as a compact one-row-per-instance table instead of the verbose block, like an OQL SELECT in a single call. On a dump where the recognized Valhalla value-flattening pattern (the JEP 401 value-classes implementation; other Valhalla features are out of scope) is detected, the filter {field:"@flattened", op:"eq", value:true} lists only flattened value instances (false lists only the ordinary aligned ones); it composes with the class name, scope, other filters, sorting, and pagination, and is rejected on any other dump.
Example output
heap_dump__get_instances(className="com.example.demo.Order", limit=1)
Resolved classes: com.example.demo.Order @ 0x7eb8dac60, com.example.demo.OrderCache @ 0x7eb8dacd8, com.example.demo.OrderEvent @ 0x7eb97b9f8 Found 50,200 instances matching 'com.example.demo.Order' (showing 1–1 by retained size): ── Instance 1: 0x7eebdce98 (com.example.demo.Order @ 0x7eb8dac60) ── Shallow: 40 Retained: 360 Fields: | Type | Name | Value | Slot | Shallow | Retained | | --------------------------------------- | --------------- | ------------------------------------------------------------------- | ---: | ------: | -------: | | java.lang.Class<com.example.demo.Order> | <class> | (java.lang.Class<com.example.demo.Order>) @ 0x7eb8dac60 | - | - | 0 | | long | id | 25901 | 8 | - | - | | java.lang.String @ 0x7eb8a118 | status | (String, length=7, backing=byte[], format=ascii-text) @ 0x7eebdcec0 | 4 | 24 | 48 | | com.example.demo.Customer @ 0x7eb8ca0d0 | customer | 0x7eb8ca248 | 4 | 32 | 152 | | byte[256] @ 0x7eb88f40 | payload | 0x7eebdcef0 | 4 | 272 | 272 | | long | createdAtMillis | 1780819196942 | 8 | - | - | … 50,199 more rows not shown (total shallow: 1.9 MB, total retained: 17.2 MB) (use offset=1 to see next page) - heap_dump__get_object_detail All fields, types, and values for a single object. Each object-field row renders its value's class in the Type column as className @ 0xHEX (distinct from the instance id in the Value column) so you can drill straight into that class. Class objects also show static fields. Also emits a Context block with the terminal GC root and hop count, immediate dominator (class plus retained size), and inbound reference count by type, so you can pick the next drill-down without an extra round-trip. When the recognized 64-bit HotSpot Valhalla value-flattening pattern (the JEP 401 value-classes implementation) is auto-detected and this object is an instance with an unaligned HPROF id, a Valhalla line is added flagging it as a flattened value.
Example output
heap_dump__get_object_detail(objectId="0x7eebdce98")
Object 0x7eebdce98 Class: com.example.demo.Order @ 0x7eb8dac60 Type: instance Reachable: yes Shallow: 40 bytes Retained: 360 bytes Context: GC root: 8 hops to jni_global → heap_dump__get_gc_root_path for full path Immediate dominator: 0x7eebdce40 java.util.HashMap$Node @ 0x7e0fe5ac8, retained 448 B → heap_dump__get_dominator_tree_node for siblings/children Inbound references: 1 (1 strong) → heap_dump__get_inbound_references for details Fields:Type Name Value Slot Shallow Retained java.lang.Class<com.example.demo.Order> <class> (java.lang.Class<com.example.demo.Order>) @ 0x7eb8dac60 - - 0 long id 25901 8 - - java.lang.String @ 0x7eb8a118 status (String, length=7, backing=byte[], format=ascii-text) @ 0x7eebdcec0 4 24 48 com.example.demo.Customer @ 0x7eb8ca0d0 customer 0x7eb8ca248 4 32 152 byte[256] @ 0x7eb88f40 payload 0x7eebdcef0 4 272 272 long createdAtMillis 1780819196942 8 - - - heap_dump__get_object_contents Render a collection's actual contents: entries of ArrayList, HashMap, IdentityHashMap, ConcurrentHashMap, arrays, StringBuilder, and Scala / Kotlin / Groovy collection types. Note: large Scala
immutable.HashMap/HashSet(CHAMP trie, more than 4 entries) andVectorare not yet rendered; useheap_dump__get_object_detailfor those. Use when: you've found a suspect cache, queue, or map and want to see what's inside, not just its size. Works on the standard JDK collections plus the major JVM languages' own types.Example output
heap_dump__get_object_contents(objectId="0x7eb8dae10", limit=5)
- java.util.HashMap @ 0x7eb8dae10 length=50000 showing [0, 5)
Key Value (String, length=11, backing=byte[], format=ascii-text) @ 0x7eb8dbaa0 (com.example.demo.Order) @ 0x7eb8dbad8 (String, length=11, backing=byte[], format=ascii-text) @ 0x7eb8db8e0 (com.example.demo.Order) @ 0x7eb8db918 (String, length=11, backing=byte[], format=ascii-text) @ 0x7eb8db720 (com.example.demo.Order) @ 0x7eb8db758 (String, length=11, backing=byte[], format=ascii-text) @ 0x7eb8db560 (com.example.demo.Order) @ 0x7eb8db598 (String, length=11, backing=byte[], format=ascii-text) @ 0x7eb8db3a0 (com.example.demo.Order) @ 0x7eb8db3d8 (49995 more - call again with offset=5, limit=5)
- heap_dump__get_inbound_references Who holds a reference to a given object, sorted by the referrer's retained size.
Example output
heap_dump__get_inbound_references(objectId="0x7eebdce98", limit=5)
Inbound References to 0x7eebdce98 (showing 1–1):
Object ID Class Field Edge Shallow (B) Retained (B) 0x7eebdce40 java.util.HashMap$Node @ 0x7e0fe5ac8 value strong 32 448
- heap_dump__get_gc_roots All GC roots grouped by root type, one entry per (root type, object) so an object rooted several ways appears once under each type. Optional
typefilter restricts to one group (jni_global, jni_local, java_frame, native_stack, sticky_class, thread_block, monitor_used, thread_obj, unknown), and applies in diff mode as well. This lists distinct rooted objects per type; the read summary's "GC roots (records)" counts raw HPROF root records and reads higher.Example output
heap_dump__get_gc_roots(limit=3)
GC Roots Report (2370 root entries)
[jni_global] - 128 object(s) (showing 1–3)
Object ID Class Shallow (B) Retained (B) 0x7eeab77a0 java.lang.Class<java.lang.String> @ 0x7eeab7830 32 576 0x7eeab91d0 sun.nio.cs.UTF_8 @ 0x7e0fe5738 24 48 0x7eeab9010 java.lang.Thread @ 0x7eeab8038 104 232 … 125 more rows not shown (total shallow: 112.5 KB, total retained: 22.0 MB)
(use offset=3 type="jni_global" to see next page)
[sticky_class] - 2226 object(s) (showing 1–3)
Object ID Class Shallow (B) Retained (B) 0x7ff805b98 java.lang.Class<jdk.internal.vm.SharedThreadContainer$$Lambda+0x000000076e1d2c00> @ 0x7eeab7830 0 0 0x7ffafc880 java.lang.Class<java.util.stream.StreamSpliterators$WrappingSpliterator$$Lambda+0x000000076e1d2948> @ 0x7eeab7830 0 0 0x7ffaed0f0 java.lang.Class<jdk.internal.vm.ThreadDumper$$Lambda+0x000000076e1d2708> @ 0x7eeab7830 0 0 … 2,223 more rows not shown (total shallow: 32.2 KB, total retained: 1.1 MB)
(use offset=3 type="sticky_class" to see next page)
(1 more root-type group omitted; output abridged)
Example output (diff of two dumps)
heap_dump__get_gc_roots(filePaths=["/data/dumps/before.hprof", "/data/dumps/after.hprof"])
Root type diff (2 snapshots): before.hprof, after.hprof
# Root type before.hprof after.hprof Δcount Δretained 1 jni_global 142 5,406 +5,264 +84.3 MB 2 java_frame 311 408 +97 +2.4 MB 3 thread_obj 24 96 +72 +6.1 MB 4 + monitor_used 0 12 +12 +48 KB Roots grouped by type, each keyed to its per-snapshot count and summed retained. Here a JNI global-reference leak dominates (jni_global +5,264).
+marks a type that appeared; unchanged types hidden unless includeUnchanged=true. The count-style diffs acceptdiffRanktoo: defaultdelta(biggest growth),value(most numerous now), orchurn(biggest movers either direction). Illustrative. - heap_dump__get_gc_root_path The exact reference chain from a GC root to a target object. Answers "why is this still alive?". By default excludes weak, soft, phantom, and finalizer edges (matching MAT's
path2gc); override withexclude_refs. Synthetic edges that no declared Java field carries (a thread holding an object via its stack, a GC-root category node holding the object it roots, an object's reference to its class, classloader, or superclass) render with a bracketed label like[stack-local],[thread-root],[gc-root], or[class]. Array-element references render as[index](the array slot), since an array slot has no field name.Example output
heap_dump__get_gc_root_path(objectId="0x7eebdce98")
GC Root Path to 0x7eebdce98 (9 steps): GC Root: jni_global [shallow=0 B / retained=0 B] └── [gc-root] jdk.internal.loader.ClassLoaders$AppClassLoader @ 0x7eeab9dc0 (class @ 0x7e0dcbfa8) [shallow=96 B / retained=22,939,448 B] └── .classes (strong) java.util.ArrayList @ 0x7eb97b9a8 (class @ 0x7eb8669a0) [shallow=24 B / retained=22,924,168 B] └── .elementData (strong) java.lang.Object[15] @ 0x7ffe031c8 (class @ 0x7eb8559c8) [shallow=80 B / retained=22,924,144 B] └── [3] java.lang.Class @ 0x7eb8dacd8 (class @ 0x7eeab7830) [shallow=8 B / retained=22,923,560 B] └── .CACHE (strong) java.util.HashMap @ 0x7eb8dae10 (class @ 0x7eb8662a0) [shallow=48 B / retained=22,923,552 B] └── .table (strong) java.util.HashMap$Node[131072] @ 0x7eb8e4038 (class @ 0x7e0fe5bb8) [shallow=524,304 B / retained=22,923,504 B] └── [88301] java.util.HashMap$Node @ 0x7eebdce40 (class @ 0x7e0fe5ac8) [shallow=32 B / retained=448 B] └── .value (strong) com.example.demo.Order @ 0x7eebdce98 (class @ 0x7eb8dac60) [shallow=40 B / retained=360 B] ← target - heap_dump__get_dominator_tree_node Navigate the dominator tree. For any node, list the objects it exclusively retains, sorted by retained size. Each retained size is also shown as a 2-decimal share of the live heap. Use when: the top-retained class list doesn't tell the full story and you want to drill into the actual object holding everything together. Start at a suspect instance and walk down to see what it's keeping alive.
Example output
heap_dump__get_dominator_tree_node(objectId="0x7eb8dae10", limit=5)
Dominator Tree Node: 0x7eb8dae10 Class: java.util.HashMap @ 0x7eb8662a0 Parent: 0x7eb8dacd8 (java.lang.Class @ 0x7eeab7830) Shallow: 48 bytes Retained: 22,923,552 bytes (41.64% of live) Children: 1 total (showing 1–1)# Object ID Class Shallow (B) Retained (B) % Live Children 1 0x7eb8e4038 java.util.HashMap$Node[131072] @ 0x7e0fe5bb8 524,304 22,923,504 41.64% 40,773
- heap_dump__get_off_heap_memory All DirectByteBuffer, NativeMemorySegment, and MappedMemorySegment allocations with sizes and addresses.
Example output
heap_dump__get_off_heap_memory()
Off-Heap Memory Report Total off-heap: 342,617,109 bytes Unique bytes (deduplicated): 196,883,576 bytes Allocations found: 19 (showing 1–19) Derived/shared: 3 allocation(s) are views into another buffer's memory By category: DirectByteBuffer 342,617,109 bytes# Object ID Class Category Native Addr Capacity (B) Retained (B) Derived File 1 0x7e0afa640 java.nio.DirectByteBufferR @ 0x7e08f1ec8 DirectByteBuffer 0x2d278e000 144,413,769 64 * 2 0x7e0afa680 java.nio.DirectByteBuffer @ 0x7eeaba670 DirectByteBuffer 0x2d278e000 144,413,769 64 3 0x7eb987220 java.nio.DirectByteBuffer @ 0x7eeaba670 DirectByteBuffer 0x7fa6ee400000 16,777,216 64 4 0x7eb987370 java.nio.DirectByteBuffer$Deallocator @ 0x7ee702e70 DirectByteBuffer 0x7fa6ee400000 16,777,216 32 5 0x7eea51b50 java.nio.DirectByteBuffer @ 0x7eeaba670 DirectByteBuffer 0x2d05e5000 8,388,608 64 6 0x7eb987288 java.nio.DirectByteBuffer @ 0x7eeaba670 DirectByteBuffer 0x7fa6ee000000 4,194,304 64 7 0x7eb987350 java.nio.DirectByteBuffer$Deallocator @ 0x7ee702e70 DirectByteBuffer 0x7fa6ee000000 4,194,304 32 8 0x7eb9872f0 java.nio.DirectByteBuffer @ 0x7eeaba670 DirectByteBuffer 0x7fa6ed1b0000 1,048,576 64 … 11 more rows (abridged) - heap_dump__get_network_connections TCP/UDP sockets (each with its instance object id and class), DNS configuration, JDWP port, and detected HTTP client libraries shown as "class @ 0xaddr" for navigation.
Example output
heap_dump__get_network_connections()
Network Connections Report
Listening Ports (1):
Object ID Local Address Class 0x7eea53ff8 0.0.0.0:51583 java.net.ServerSocket @ 0x7eea41958 Active TCP Connections (1):
Object ID Local Remote Class 0x7eeaa1c98 *:51583 127.0.0.1:51584 java.net.Socket @ 0x7eea84ff0 UDP Sockets (1):
Object ID Local Address Class 0x7eea6d310 0:0:0:0:0:0:0:0:57059 sun.nio.ch.DatagramChannelImpl @ 0x7eea399b8 JDWP: not detected
DNS Nameservers: not detected
HTTP Client classes: none detected
- heap_dump__get_threads All threads with stack traces and retained sizes (also shown as a 2-decimal share of the live heap), sortable by retained or name. Each row reports the thread state (RUNNABLE, WAITING, TIMED_WAITING, BLOCKED), daemon flag, and priority when the dump records them, including JDK 19+ dumps where those fields moved into the thread's field holder. Compact summary mode and per-thread frame_limit keep large dumps inline; auto-switches to summary if the full response would exceed the inline cap.
Example output
heap_dump__get_threads(limit=3, frame_limit=5)
Threads (16 total, showing 1–3, sorted by retained size) Thread "batch-worker-1" [java.lang.Thread @ 0x7eeab8038] state=WAITING [alive, waiting, waiting indefinitely, parked] daemon=true priority=5 retained=6,291,824 bytes (11.43% of live), shallow=104 bytes (0x7eea519c8) at jdk.internal.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:223) at com.example.demo.DemoApp.lambda$startWorkerThreads$0(DemoApp.java:281) at com.example.demo.DemoApp$$Lambda+0x000000076e109a30.run(<unknown>) at java.lang.Thread.runWith(Thread.java:1487) ... (1 more frames; pass frame_limit=0 for full stack) Thread "batch-worker-3" [java.lang.Thread @ 0x7eeab8038] state=WAITING [alive, waiting, waiting indefinitely, parked] daemon=true priority=5 retained=6,291,824 bytes (11.43% of live), shallow=104 bytes (0x7eea6ac90) at jdk.internal.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:223) at com.example.demo.DemoApp.lambda$startWorkerThreads$0(DemoApp.java:281) at com.example.demo.DemoApp$$Lambda+0x000000076e109a30.run(<unknown>) at java.lang.Thread.runWith(Thread.java:1487) ... (1 more frames; pass frame_limit=0 for full stack) Thread "batch-worker-2" [java.lang.Thread @ 0x7eeab8038] state=WAITING [alive, waiting, waiting indefinitely, parked] daemon=true priority=5 retained=6,291,824 bytes (11.43% of live), shallow=104 bytes (0x7eea38718) at jdk.internal.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:223) at com.example.demo.DemoApp.lambda$startWorkerThreads$0(DemoApp.java:281) at com.example.demo.DemoApp$$Lambda+0x000000076e109a30.run(<unknown>) at java.lang.Thread.runWith(Thread.java:1487) ... (1 more frames; pass frame_limit=0 for full stack) … 13 more rows not shown (total shallow: 1.3 KB, total retained: 12.1 KB) (use offset=3 to see next page)
- heap_dump__get_class_loaders All ClassLoader subtypes with their JAR/URL paths. Filters JDK builtin loaders by default (pass excludeJdk=false to include) and supports className and loaderName substring filters. The excludeJdk, className, and loaderName filters apply in diff mode as well. The footer reconciles two counts: total ClassLoader instances on the heap, and how many defined at least one class (the figure shown in the read summary).
Example output
heap_dump__get_class_loaders()
Class Loader Report
Class Loaders (2):
Object ID Class Classes Loader Name Parent ID URLs 0x7eea38930 java.net.URLClassLoader @ 0x7eea39418 1 plugin-loader-2 0x7eeab9dc0 /opt/order-service/plugins/demo-plugin.jar 0x7eea38a58 java.net.URLClassLoader @ 0x7eea39418 1 plugin-loader-1 0x7eeab9dc0 /opt/order-service/plugins/demo-plugin.jar (filtered 3 JDK builtin loader instances; pass excludeJdk=false to include)
Loader counts: 5 ClassLoader instances; 3 defined ≥1 class.
- heap_dump__get_duplicate_classes Classes loaded by more than one ClassLoader. A common cause of ClassCastException in OSGi and plugin frameworks.
Example output
heap_dump__get_duplicate_classes()
Duplicate class definitions (1 classes loaded by multiple ClassLoaders):
Class Defs Instances Shallow (B) Retained (B) com.example.plugin.PluginTask 2 2 32 352 - heap_dump__get_duplicate_strings String instances with identical content backed by separate char arrays, wasting heap. Sorted by wasted bytes. In diff mode (two or more dumps) it compares live-only wasted-byte totals, and includeUnreachable is not supported.
Example output
heap_dump__get_duplicate_strings(sortBy="wasted", limit=5)
Duplicate String Groups: 1,255 groups, 356.0 KB total wasted (showing 1–5)
Sorted by: wasted bytes (best dedup ROI first)
# Instances Arrays Length (chars) Encoding Wasted (B) Total (B) Sample String Object IDs 1 16,668 16,668 7 LATIN1 116,669 116,676 0x7e0000190, 0x7e0000890, 0x7e0000dd0 (+16665 more) 2 16,667 16,667 7 LATIN1 116,662 116,669 0x7e00006d0, 0x7e0000c10, 0x7e0001310 (+16664 more) 3 16,668 16,668 6 LATIN1 100,002 100,008 0x7e0000350, 0x7e0000510, 0x7e0000a50 (+16665 more) 4 18 18 43 LATIN1 731 774 0x7eb9d8cc0, 0x7eb9d8d80, 0x7eb9d9230 (+15 more) 5 39 39 13 LATIN1 494 507 0x7eb9d3110, 0x7eb9d5fd0, 0x7eb9f4298 (+36 more) … 1,250 more groups not shown (total wasted: 29.3 KB)
(use offset=5 to see next page)
Note: String content withheld (security).
- heap_dump__get_system_properties JVM system properties as key=value pairs (sensitive values redacted by default).
Example output
heap_dump__get_system_properties()
JVM System Properties (56 entries):
Key Value file.encoding UTF-8 java.version 25.0.2 java.vm.name OpenJDK 64-Bit Server VM java.vm.vendor Eclipse Adoptium os.name Mac OS X os.version 15.7.4 sun.java.command (String, REDACTED) @ 0x7eb8a08f8 user.dir (String, REDACTED) @ 0x7eb8a1370 … 48 more rows (abridged) Example output (diff of two dumps)
heap_dump__get_system_properties(filePaths=["/data/dumps/before.hprof", "/data/dumps/after.hprof"])
System properties diff (2 snapshots): before.hprof, after.hprof
key before.hprof after.hprof ~ java.runtime.version 21.0.4+7-LTS 21.0.7+9-LTS ~ java.version 21.0.4 21.0.7 + app.rollout.canary (absent) (String, REDACTED) - jdk.tls.client.protocols (String, REDACTED) (absent) Grouped changed / added (
+) / removed (-);(absent)= absent in that dump. Values are redacted per-side exactly as in single-dump mode, so a change surfaces only when the visible value changes (a JVM patch here). Unchanged keys hidden unless includeUnchanged=true. Illustrative. - heap_dump__get_system_environment OS environment variables visible to the JVM process.
Example output
heap_dump__get_system_environment()
OS Environment Variables (37 entries):
Key Value HOME (String, REDACTED) @ 0x7ffe4ca30 JAVA_HOME (String, REDACTED) @ 0x7ffe4b9d8 LANG (String, REDACTED) @ 0x7ffe4bf70 PATH (String, REDACTED) @ 0x7ffe4c570 SHLVL 2 USER (String, REDACTED) @ 0x7ffe4b530 … 31 more rows (abridged) - heap_dump__get_jfr_recordings Active JFR (Java Flight Recorder) recordings, their state, and event settings. The Object line is the jdk.jfr.internal.PlatformRecording instance, and the Settings line shows the settings map as "class @ 0xaddr" so you can navigate to it. With the injection gate on, the recording name is redacted to its heap address (so you can still navigate to it), settings collapse to an entry count, and the state is shown only when it matches a known RecordingState value.
Example output
heap_dump__get_jfr_recordings()
JFR Recordings: 1 found Recording #1: "(String, REDACTED) @ 0x6c1a3f8d0" Object: jdk.jfr.internal.PlatformRecording @ 0x7eb88ab18 Recording ID: 1 State: RUNNING To disk: true Dump on exit: false Max size: unlimited Duration: unlimited Max age: unlimited Settings: java.util.LinkedHashMap @ 0x6c1a41200 (3 entries, content redacted by the string-hiding gate) - heap_dump__get_java_agents Attached Java agents loaded via -javaagent, -agentpath, or -agentlib flags. The Source column shows the heap object each entry came from as "class @ 0xaddr" (the JVM-arg String, or the InstrumentationImpl instance), so you can navigate to it. With the injection gate on, the agent name, path, and options are redacted; the type and Source columns are always shown.
Example output
heap_dump__get_java_agents()
Java Agents: 1 found
Type Agent Name Path Options Source instrumentation <unknown> <detected> sun.instrument.InstrumentationImpl @ 0x7c0044e80 - heap_dump__get_jmx_mbeans JMX MBean attributes (primitives, Strings, AtomicLong/Integer), filterable by domain or object name (both filters apply in diff mode as well). The resource class is shown as "class @ 0xaddr", where the address is the resource instance whose fields are the listed attributes. Compact summary mode groups MBeans by domain without attribute values; auto-switches to summary if the full response would exceed the inline cap. With the injection gate on, MBean object names, map-entry keys, and non-numeric attribute values are redacted; plain attribute names and resource class names are class metadata and stay visible.
Example output
heap_dump__get_jmx_mbeans(domain="com.example")
JMX MBeans (1 found, domain filter: com.example): ── com.example:type=CacheStats (com.example.demo.CacheStats @ 0x7f3a210b8) ── cachedOrders 50000 hitRate 0.97 evictionCount 1342(output abridged)
- heap_dump__get_modules Java modules (JPMS) loaded by the JVM, with the Module instance object id, module name, version, main class, the ClassLoader that defines each module, and a count of the module's ServiceLoader bindings. The Provides column counts the
provides <service> with <impl>directives from each module-info, and a detail section below the table expands them toservice -> provider classes. This surfaces the service providers registered on the module path (JDBC drivers, filesystem providers, application SPIs). It does not cover classpath (META-INF/services) providers, which are not stored in a ModuleDescriptor. Returns empty on JDK 8 and earlier, where the module system was not yet introduced.Example output
heap_dump__get_modules(limit=8)
Java Modules (70) (showing 1–8):
Module Name Object ID Version Main Class Loader Object ID Provides (unnamed) 0x7eeac1100 - - (bootstrap) - jdk.compiler 0x7eeac1340 25.0.2 - 0x7eeab9dc0 2 java.logging 0x7eeac1580 25.0.2 - (bootstrap) - (unnamed) 0x7eeac17c0 - - (bootstrap) - jdk.proxy1 0x7eeac1a00 - - (bootstrap) - jdk.jfr 0x7eeac1c40 25.0.2 jdk.jfr.internal.tool.Main (bootstrap) - jdk.management.jfr 0x7eeac1e80 25.0.2 - (bootstrap) - jdk.management 0x7eeac20c0 25.0.2 - (bootstrap) 1 … 62 more rows not shown
(use offset=8 to see next page)
Service providers (module -> service -> providers):
- jdk.compiler: - com.sun.source.util.Plugin -> (none) - java.util.spi.ToolProvider -> com.sun.tools.javac.main.JavacToolProvider - jdk.management: - sun.management.spi.PlatformMBeanProvider -> com.sun.management.internal.PlatformMBeanProviderImpl
- heap_dump__get_service_loaders Live
java.util.ServiceLoaderinstances in the heap, with the service type each loads, its defining ClassLoader, and the providers it discovered. A detail section expands each loader asservice -> provider class [instance], marking whether each provider was actually instantiated or only discovered. This is the runtime, resolved view of the JDK ServiceLoader mechanism. Unlike get_modules (which reports the static module-infoprovidesdirectives), it also covers classpath (META-INF/services) providers, but only for services the application actually loaded.Example output
heap_dump__get_service_loaders(limit=8)
Java ServiceLoaders (12) (showing 1–8):
Service ServiceLoader Object ID Loader Object ID Providers java.nio.file.spi.FileSystemProvider 0x7ef012340 (bootstrap) 2 java.time.chrono.Chronology 0x7ef014880 (bootstrap) 4 org.example.spi.ReportRenderer 0x7ef0161c0 0x7ee9ad200 1 … 9 more rows not shown
(use offset=8 to see next page)
Providers (service -> provider [instance]):
- org.example.spi.ReportRenderer: - org.example.impl.HtmlReportRenderer @ 0x7ee9c4410 (instantiated) - org.example.impl.PdfReportRenderer (not instantiated)
Thread dump
- thread_dump__read Parse a jstack/jcmd text dump or a JEP 425 JSON dump (
jcmd Thread.dump_to_file -format=json). The JSON format scales to millions of virtual threads (Loom). Required first step. Returns total thread count, breakdown by state and container, runtime version, dump timestamp, format, and top stack-trace clusters. Input must be a regular file (fifos, sockets, and char devices are rejected) and must be plain text; files with non-text bytes in the first 4 KB are rejected. Default size cap is 256 MB, configurable via-Dio.deepheap.threadDump.maxFileBytes=<bytes>(0disables).Example output
thread_dump__read(filePath="/data/dumps/demo.threads.txt")
Thread dump summary
Property Value Threads 20 Virtual 0 Format TEXT Runtime Full thread dump OpenJDK 64-Bit Server VM (25.0.2+10-LTS mixed mode): Dump time 2026-06-07 11:11:43 Threads by state:
State Count RUNNABLE 11 WAITING (parking) 4 TIMED_WAITING (on object monitor) 2 BLOCKED (on object monitor) 1 TIMED_WAITING (sleeping) 1 WAITING (on object monitor) 1 Top stack-trace clusters (threads sharing same top frames): Cluster 1 - 8 threads (e.g. Signal Dispatcher, Service Thread, Monitor *** Thread): Cluster 2 - 3 threads (e.g. batch-worker-1, batch-worker-2, batch-worker-3): at jdk.internal.misc.Unsafe.park(java.base@25.0.2/Native Method) at java.util.concurrent.locks.LockSupport.park(java.base@25.0.2/LockSupport.java:223) at com.example.demo.DemoApp.lambda$startWorkerThreads$0(DemoApp.java:281) at 0x000000076e109a30.run(Unknown Source) at java.lang.Thread.runWith(java.base@25.0.2/Thread.java:1487) - thread_dump__list_threads Paginated thread list with name, tid, state, virtual flag, container, and stack frames. Filter by name substring or state (RUNNABLE / WAITING / BLOCKED).
Example output
thread_dump__list_threads(nameFilter="batch-worker", limit=3)
Thread dump threads (3 total, showing 1–3) Thread "batch-worker-1" tid=140354837821952 [daemon] state=WAITING (parking) prio=5 at jdk.internal.misc.Unsafe.park(java.base@25.0.2/Native Method) at java.util.concurrent.locks.LockSupport.park(java.base@25.0.2/LockSupport.java:223) at com.example.demo.DemoApp.lambda$startWorkerThreads$0(DemoApp.java:281) at 0x000000076e109a30.run(Unknown Source) at java.lang.Thread.runWith(java.base@25.0.2/Thread.java:1487) at java.lang.Thread.run(java.base@25.0.2/Thread.java:1474) Thread "batch-worker-2" tid=140354837824000 [daemon] state=WAITING (parking) prio=5 at jdk.internal.misc.Unsafe.park(java.base@25.0.2/Native Method) at java.util.concurrent.locks.LockSupport.park(java.base@25.0.2/LockSupport.java:223) at com.example.demo.DemoApp.lambda$startWorkerThreads$0(DemoApp.java:281) at 0x000000076e109a30.run(Unknown Source) at java.lang.Thread.runWith(java.base@25.0.2/Thread.java:1487) at java.lang.Thread.run(java.base@25.0.2/Thread.java:1474) Thread "batch-worker-3" tid=140354837809664 [daemon] state=WAITING (parking) prio=5 at jdk.internal.misc.Unsafe.park(java.base@25.0.2/Native Method) at java.util.concurrent.locks.LockSupport.park(java.base@25.0.2/LockSupport.java:223) at com.example.demo.DemoApp.lambda$startWorkerThreads$0(DemoApp.java:281) at 0x000000076e109a30.run(Unknown Source) at java.lang.Thread.runWith(java.base@25.0.2/Thread.java:1487) at java.lang.Thread.run(java.base@25.0.2/Thread.java:1474)
GC log
- gc_log__read Parse a Java GC log file and return a full diagnostic report. Accepts plain text, gzipped (
.gz), and rotating log sets (auto-detects siblings likegc.log.0..Nand.currentin the same directory; disable withfindRotated=false). Auto-detects unified (JDK 9+) and pre-unified (JDK 8) formats; supports G1, Parallel, Serial, CMS, ZGC, and Shenandoah, plus Azul Zing C4 (basic metrics). The response includes a TL;DR, collector overview, throughput, pause stats by category, cause breakdown, heap trend, allocation/promotion rates, reclamation efficiency, concurrent phase stats, G1 region stats, heap sizing recommendation, CPU time, and safepoint stats, all in one call. When--rootis set (either transport), bothfindRotated=trueand directory inputs are refused (rotation directory walking cannot be safely contained); passfindRotated=falseand a single file path per call. Plain-text inputs must begin with a recognizable GC log header (unified[timestamp]/[uptime]or pre-unified date stamp/uptime) within the first 4 KB; non-text or unrecognized files are rejected. Default size cap is 4 GB per file, configurable via-Dio.deepheap.gcLog.maxFileBytes=<bytes>(0disables).Example output
gc_log__read(filePath="/data/dumps/demo.gc.log")
TL;DR
- Heap trend: GROWING - 104.0 MB → 128.0 MB across 4s (consider a heap dump from this JVM). - Humongous allocations: 61 events, peak 112 regions (2.0 MB each, threshold ≥ 1.0 MB) - peak live humongous footprint ≈ 224.0 MB in old gen (split across an unknown number of objects, each ≥ 1.0 MB; GC logs do not report per-object sizes) - consider larger -XX:G1HeapRegionSize or reduce large object allocation size.File
Property Value Path /data/dumps/demo.gc.log Size 749.8 KB Last modified 2026-06-07T15:14:00.057Z Files parsed Single file Log starts 2026-06-07T15:13:55.076Z (uptime 0.353s) Log ends 2026-06-07T15:13:59.996Z (uptime 5.273s) Log span 4s GC Summary
Metric Value Collector G1 Format Unified (JDK 9+) Total events 480 STW events 180 Throughput 96.25% Total STW time 184ms G1 heap region size 2.0 MB Humongous threshold ≥ 1.0 MB Pause Summary by Category
Category Count Min p50 p95 p99 Max Total Young 60 0.9 ms 1.7 ms 3.7 ms 5.1 ms 5.1 ms 117ms Full GC 1 3.4 ms 3.4 ms 3.4 ms 3.4 ms 3.4 ms 3ms Remark / InitialMark 119 0.1 ms 0.6 ms 1.1 ms 1.3 ms 1.4 ms 64ms Concurrent 300 0.0 ms 0.5 ms 1.7 ms 2.9 ms 169.6 ms 499ms Note: Concurrent phase durations include wall-clock time, not application pause time. Throughput is computed from STW categories only.
Heap Trend (after-GC occupancy)
Metric Value Trend GROWING Start 104.0 MB End 128.0 MB Data points 180 Mean 124.7 MB p95 179.0 MB Peak 224.0 MB Growing live data across the log duration - consider loading a heap dump captured from the same JVM to identify the leaking class.
(report continues: cause breakdown, allocation/promotion rates, reclamation efficiency, G1 region stats, sizing recommendation, CPU and safepoint stats; output abridged)
- gc_log__get_pause_outliers Top-N STW events exceeding a duration threshold (default: max(200 ms, p99 × 2)), with timestamp, cause, heap before/after, and sys/user CPU time. Use when the report shows extreme individual pauses that need per-event detail.
Example output
gc_log__get_pause_outliers(thresholdMs=2, limit=5)
Top pause outliers (threshold 2 ms, showing 5 of 24)
# Duration Category Cause Timestamp Heap Before Heap After User CPU Kernel CPU 1 5.1 ms Young G1 Humongous Allocation 1.628 s 231.0 MB 102.0 MB 0.010 s 0.000 s 2 3.9 ms Young G1 Humongous Allocation 1.296 s 231.0 MB 98.0 MB 0.000 s 0.000 s 3 3.7 ms Young G1 Humongous Allocation 0.353 s 232.0 MB 104.0 MB 0.000 s 0.010 s 4 3.6 ms Young G1 Humongous Allocation 0.449 s 231.0 MB 98.0 MB 0.000 s 0.000 s 5 3.4 ms Full GC Heap Dump Initiated GC 3.835 s 181.0 MB 112.0 MB 0.010 s 0.010 s … 19 more outliers not shown
- gc_log__get_safepoint_outliers Top-N non-GC safepoint events (RevokeBias, ThreadDump, Deoptimize, …) exceeding a stopped-time threshold (default: max(50 ms, p99 × 2)), with trigger name, TTSP, and timestamp. GC pauses are excluded. Use when the TL;DR flags a costly non-GC safepoint trigger.
Example output
gc_log__get_safepoint_outliers(thresholdMs=0.1, limit=5)
Top safepoint outliers (threshold 0 ms, showing 5 of 8)
# Stopped Trigger TTSP Timestamp 1 0.4 ms RevokeBias 0.3 ms 3.108 s 2 0.2 ms RevokeBias 0.2 ms 0.166 s 3 0.2 ms RevokeBias 0.1 ms 3.108 s 4 0.2 ms EnableBiasedLocking 0.1 ms 0.120 s 5 0.2 ms RevokeBias 0.0 ms 3.109 s … 3 more outliers not shown
- gc_log__get_humongous_events Top-N G1 pause events ranked by the per-pause snapshot count of humongous regions live in old gen, with cause, timestamp, duration, and heap before/after. The count is a snapshot, not a per-pause allocation delta (one large object can appear in many rows until reclaimed). G1 only. Use when the TL;DR flags a large humongous footprint in old gen.
Example output
gc_log__get_humongous_events(limit=5)
Top humongous events (min 1 region, showing 5 of 61)
# Hum. regions live Before → After Cause Timestamp Duration Heap Before → After 1 112 112 → 112 G1 Humongous Allocation 4.080 s 1.3 ms 225.0 MB → 224.0 MB 2 84 114 → 84 G1 Humongous Allocation 3.828 s 2.0 ms 229.0 MB → 168.0 MB 3 84 114 → 84 G1 Humongous Allocation 2.481 s 1.2 ms 229.0 MB → 168.0 MB 4 83 113 → 83 G1 Humongous Allocation 3.478 s 2.0 ms 227.0 MB → 166.0 MB 5 83 113 → 83 G1 Humongous Allocation 2.120 s 1.7 ms 227.0 MB → 166.0 MB … 56 more events not shown
`Hum. regions live` is the snapshot count of regions occupied by humongous objects at this GC - not the size or count of objects allocated this pause. A single large object survives across many GCs and shows up in every row until reclaimed. To learn the size of any individual humongous object, load a heap dump and inspect arrays by retained size.
- gc_log__get_allocation_spikes Top-N allocation-rate intervals (inter-Young-GC windows) exceeding a rate threshold (default: max(500 MB/s, p99 × 1.5)), with rate, bytes allocated, window timestamps, and duration. Use when the TL;DR flags a spikey or high allocation rate.
Example output
gc_log__get_allocation_spikes(minRateMbs=100, limit=5)
No allocation intervals exceeded 100 MB/s (threshold = max(500, p99×1.5) = 100 MB/s).
FAQ & troubleshooting
Common setup, debugging, and update questions.
- Double-check the absolute path to the
mcpbinary in your client config. Relative paths and~aren't expanded by most clients. - Restart the client after editing the config; most MCP clients only read it on launch.
- Run the binary manually first:
/path/to/deepheap/bin/mcp --helpshould print usage. If it doesn't, the install is incomplete. - On macOS, you may need to remove the quarantine attribute:
xattr -d com.apple.quarantine /path/to/mcp. - Check your client's MCP log. Most clients surface stderr from the server there, which usually identifies the issue immediately.
- deepheap is out-of-core: it memory-maps the dump rather than loading the full file into RAM, and spills its largest derived indexes to disk. Footprint scales with object count, not file size, so dumps far larger than RAM are routine. Dumps with very high object counts (billions) need more headroom for the dominator build; if a load struggles, check for swap pressure or anti-virus scanners reading the file.
- The initial
heap_dump__readcall builds the dominator tree, which is the expensive step. Multi-gigabyte dumps can take several minutes. - Subsequent calls are fast; the parsed snapshot is cached and reused for the lifetime of the deepheap process.
- SSDs make a large difference. The graph build is bound on random read latency more than throughput.
Use jcmd on the running JVM. Find the PID with jps, then:
jcmd <pid> GC.heap_dump /path/to/app.hprof
By default this triggers a full GC first, so only live objects end up in the dump. That's usually what you want for leak analysis.
To capture everything on the heap, including unreachable objects waiting to be collected, skip the GC:
jcmd <pid> GC.heap_dump -all /path/to/app.hprof
The post-GC dump is also significantly smaller on disk, since unreachable objects are gone before the snapshot is written. If you do capture a -all dump, deepheap can still narrow analysis to reachable objects: most tools accept a scope parameter (live / garbage / all), so you can keep the full dump for forensics and filter at query time.
Alternative: -XX:+HeapDumpOnOutOfMemoryError as a JVM flag writes a dump automatically the next time the JVM throws OutOfMemoryError.
Use jcmd on the running JVM. Find the PID with jps, then:
For a plain-text thread dump:
jcmd <pid> Thread.print > /path/to/threads.txt
For the JSON format (JEP 425, JDK 21+), which is the only one that scales to millions of virtual threads:
jcmd <pid> Thread.dump_to_file -format=json /path/to/threads.json
If you suspect a deadlock or a stuck thread, take three dumps about 5 seconds apart. Comparing them shows which threads are actually moving and which are pinned in one spot.
On JDK 9+, use the unified logging framework. Add this to the JVM command line:
-Xlog:gc*,gc+ref=debug,gc+phases=debug,gc+age=trace,safepoint:file=gc-%t.log:time,uptime,level,tags:filecount=10,filesize=50M
What each piece does:
gc*: all GC events at info level.gc+ref=debug: reference processing (weak/soft/phantom) timings, often a hidden source of pause time.gc+phases=debug: per-phase breakdown of each pause.gc+age=trace: object aging in young generations, useful for tenuring threshold tuning.safepoint: safepoint sync and stop times, which catch non-GC pauses.file=gc-%t.log: an absolute or relative path;%texpands to a timestamp so restarts don't overwrite. Usefile=/var/log/myapp/gc-%t.logfor a fixed location.filecount=10,filesize=50M: rotate after 50 MB, keep 10 files.
On JDK 8 the equivalent is -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=50M. deepheap reads both formats.
- Heap dumps: HotSpot HPROF binary only, what you get from
jmap -dump:format=b,jcmd <pid> GC.heap_dump, or-XX:+HeapDumpOnOutOfMemoryErroron a HotSpot-based JVM (OpenJDK, Oracle JDK, Adoptium Temurin, Amazon Corretto, Azul Zulu, etc.). JDK 8 dumps through JDK 25 dumps all work. Azul Zing / Platform Prime dumps also load: Zing uses a different object model, which deepheap detects and sizes correctly, and its C4 collector is identified in the summary. IBM J9 / OpenJ9 PHD dumps and GraalVM native-image heap dumps are not supported (GraalVM running on HotSpot emits standard HPROF, which loads). - JVM languages: the HPROF format is language-agnostic, so dumps from Scala, Kotlin, Groovy, Clojure, JRuby, Jython, etc. all load. deepheap has dedicated collection renderers for Scala, Kotlin, and Groovy types (cons lists, Map1–4, Set1–4, Tuple, Option, Either, Pair, Triple, GString, …) so their contents render as entries rather than internal Java fields. Detected languages and versions appear in the summary that
heap_dump__readreturns after loading. - Thread dumps: jstack /
jcmd Thread.printtext format, and the JEP 425 JSON format (jcmd Thread.dump_to_file -format=json). The JSON format is the only one that scales to millions of virtual threads. - GC logs: plain text and gzipped (
.gz), unified (JDK 9+) and pre-unified (JDK 8). Collectors: G1, Parallel, Serial, CMS (removed in JDK 14, logs still parse), ZGC, Shenandoah, Azul Zing C4 (basic metrics).
- The deepheap process runs entirely on your machine and does not phone home.
- But your AI client does. Claude Code, Cursor, etc. send your prompts and the tool responses to their model provider over the network.
- Tool responses can include class names, field values, retained-size figures, and (with
--show-strings) raw heap content. Treat the redaction defaults accordingly. - For sensitive heap dumps from production, leave the defaults in place. The redaction gates are conservative by design. String content, byte[], char[], and short[] are summarized rather than emitted verbatim. See the next FAQ entry for details.
- For extra-sensitive dumps (financial, medical, regulated data), also pass
--show-primitives=false. Scalar primitive values and numeric array contents (int[], long[], …) are shown by default and may leak IDs, timestamps, or numeric payloads that the string gate does not cover. - HTTP mode has no authentication. The default bind is
localhost.--rootlimits which files the server is willing to load and--allowed-originrestricts the CORS Origin header, but neither is a substitute for authentication on a network-exposed port.
Two independent gates control what reaches the AI. Both are ON by default.
- Injection gate: prevents raw heap strings from reaching the model context. String/StringBuilder/StringBuffer and primitive byte[]/char[]/short[] arrays are replaced with a shape summary plus a closed-vocabulary format hint (json, xml, jwt, base64, …). The model sees the shape, not the content. This guards against two distinct risks: data exfiltration (a malicious prompt coaxing the model into echoing PII, tokens, or session content) and context poisoning (heap-resident strings carrying attacker-controlled prose, e.g. logged HTTP request bodies or user input, that could steer the model into ignoring instructions, calling tools maliciously, or hallucinating). A raw heap is an untrusted input channel even when the application is friendly.
--show-stringsdisables this gate; only use with fully trusted dumps. - Sensitive-name gate: applies a second pass that redacts field and map values whose name matches a credential pattern (password, token, secret, key, …).
--show-secretsdisables this. --show-primitives=falsecloses a third channel that the injection gate does not cover: scalar primitive field values (int, long, boolean, char, …) and all primitive arrays (int[], long[], float[], …). Shown by default. Disable for extra-sensitive dumps where numeric IDs, timestamps, or array contents must never be surfaced.
- deepheap is written using Java language features introduced in JDK 25, so the runtime needs to be JDK 25 or later.
- The heap dumps themselves can come from any JVM version; deepheap parses HPROF files from JDK 8 onward.
- If JDK 25 isn't your default JVM, you can install it side-by-side (e.g. via Adoptium Temurin) and point only deepheap at it; the rest of your toolchain is unaffected.
- Download the new zip from the Downloads page and unzip over the existing install (or to a new path).
- Restart your MCP client (or stop the running server) so it launches the new binary on the next tool call.
- If you're running HTTP mode, stop the running process and start the new one; there's no rolling update story for HTTP mode.