Java Thread Dump Inspector
Reads Java thread dumps — text or JSON — and points at what is wrong.
One HTML file. Runs in your browser.
A service stops responding, a pool takes no new work, two threads deadlock. You take a thread
dump and get thousands of lines. This reads them: which pools are saturated, who holds the lock
everyone is waiting on, where the threads pile up.
[ Open the inspector ]
No dump at hand? A sample one is built in — open it and look around.
| Before using it |
After using it |
 |
 |
The dump never leaves your machine
Parsed in the browser. No upload, no backend, no analytics, no external request —
not even a font. Save the page and it works offline.
Which dumps it reads
| Taken with |
JDK 8, 17 |
JDK 21 |
JDK 25–28 |
| jstack / jcmd Thread.print |
yes | yes | yes |
| jcmd Thread.dump_to_file -format=json |
no such command | yes, no states in that format | yes, full data |
| jcmd Thread.dump_to_file -format=plain |
no such command | recognised, asks you to re-take as json |
Tested on Temurin 17, 21, 25 and 26, Liberica 8, JetBrains Runtime 25, and OpenJDK
early-access 27 and 28.
jstack cannot see virtual threads — on any JDK from 21 to 28. Against an
application built on them it shows the carrier threads and nothing of what runs on them. That is
deliberate: JEP 425 says traditional thread dumps will
not be extended to virtual threads, since a flat list does not scale to millions of them.
The dump that does show them is
jcmd <pid> Thread.dump_to_file -format=json dump.json — every virtual thread,
grouped by its thread container, and written without pausing the application. Load that file
here.
It is not a superset of jstack, though: it lists Java threads, so the JVM's own
— GC, JIT compiler, VM Thread — are missing from it. For a GC or JIT question, take
both.
Tabs
| Tab |
What it shows |
| Findings | What looks wrong, ranked. Start here. |
| Pools | Which pools are saturated, and what their busy threads do. |
| Population | Which thread groups exist and how big they are. |
| Top frame | Where threads sit right now, most crowded first. |
| States | State counts, plus a Thread.State reference. |
| Creation order | Threads in dump order. Leaks show up as a growing tail. |
| Locks | Who waits on which lock, and who holds it. |
| Stack search | Regex over full stacks. |
| Compare | Two dumps: what grew, what went away. |
Findings
Most checks are generic: a pool with every worker busy, threads piling up in one frame,
several waiters on one lock, a deadlock the JVM reported itself.
Checks that match frames of a specific library carry a signature: badge, so a
library-specific guess never reads as a universal truth. Only HikariCP ships built in —
narrower ones are pasteable rules, below.
Custom rules
Add your own without touching the source. Findings > Custom rules > + New rule.
Write the body of function (d, helpers):
var starved = d.threads.filter(function (t) {
return t.state === "BLOCKED" && t.top.indexOf("com.acme.cache.") === 0;
});
if (starved.length < 5) return [];
return [{
level: "alert",
title: nThreads(starved.length) + " blocked on the cache lock",
text: "Find the holder in the Locks tab first.",
items: starved
}];
|
Return one finding, an array of them, or nothing when the dump does not match.
Every thread field and helper is listed in the editor, under How to write a rule, and in
the full
reference.
Rules live in localStorage: your browser only, never sent anywhere. A rule that throws becomes
its own finding card instead of breaking the tab. Ready-made ones for specific libraries are in
rules/
— paste a file into the form to use it.
Source on GitHub |
Contributing a rule |
MIT
|