Chris Collins

Tools · infra

Java thread dump analyser

Paste jstack, jcmd Thread.print, kill -3 or Thread.dump_to_file (text or JSON, with virtual threads) output: threads grouped by state and identical stack, deadlocks and a lock graph, thread pools, hot frames, CPU per thread, and a comparison of dumps taken seconds apart to find stuck threads.

Added

Works with

How to use it

A thread dump is the first thing I take when a Java service hangs or burns CPU, and the last thing I want to read line by line. This page reads it for you: it groups the threads, follows the locks and tells you what is stuck.

How to use it

Take a dump and paste it, drop the file, or open several files at once:

jstack -l <pid> > dump.txt                     # -l adds java.util.concurrent lock owners
jcmd <pid> Thread.print -l > dump.txt          # the same, through jcmd
kill -3 <pid>                                  # printed to the JVM's stdout (container logs)
jcmd <pid> Thread.dump_to_file -format=json threads.json   # JDK 21+: includes virtual threads

A file with several dumps in it (kill -3 a few times) is split into separate dumps. Example loads two real dumps of a small service I wrote for this page, taken 1.3 seconds apart.

  • Overview — threads by state, a bar of RUNNABLE / BLOCKED / WAITING / TIMED_WAITING, the findings (deadlocks, a thread holding a lock others wait for, saturated pools, threads using a lot of CPU), the thread pools recognised by name (Tomcat, Jetty, Undertow, HikariCP, Netty and Reactor Netty, ForkJoin, Executors, Spring, gRPC, Kafka, Lettuce…) and CPU time per thread where the dump has cpu= and elapsed= (jstack from JDK 11 on).

  • Threads — threads with identical stacks grouped, biggest group first, idle pool workers and JVM-internal threads last. Filter by name, frame or lock address, state, or what the thread is doing: running, blocked, waiting, sleeping, in I/O, idle (a pool worker waiting for work, a selector, accept) or JVM.

  • Locks — a graph of each contended lock: who holds it, who waits for it, and a table of every lock held. Monitors released by Object.wait() are not counted as held.

  • Deadlocks — cycles of threads each waiting for a lock the next one holds, drawn as a cycle, next to the JVM’s own "Found one Java-level deadlock" report when the dump has one. Without -l the dump doesn’t name the owners of ReentrantLock and friends; the page then uses the JVM’s report to fill them in.

  • Hot frames — where the busy threads are: the top frame, and the first frame outside the JDK and common frameworks.

  • Compare — with two or more dumps: threads matched across dumps, flagged busy (same stack, CPU rising — a hot loop), stuck (same stack, blocked or in I/O or running without CPU), waiting or sleeping in the same place; CPU % of a core between the first and last dump; threads that appeared or went away.

Plan memory for N threads opens the JVM memory planner with the thread count. If you paste a GC log here by mistake, the page offers to open it in the GC log analyser; Native Memory Tracking output goes to the memory planner.

Formats

I checked the parser against dumps I captured from JDK 8, 11, 17, 21 and 24 (jstack, jstack -l, jcmd Thread.print, kill -3 output, and Thread.dump_to_file text and JSON). The JSON format of JDK 21–24 has no thread states or locks: states are then guessed from the top frame and marked as such. Newer JDKs add state, parkBlocker, blockedOn and monitorsOwned; the page reads them when they are there. IBM/OpenJ9 javacores are a different format and are not read.

Browser support

Any current browser. Nothing to install.

Privacy

Everything happens in this page. The dumps are not uploaded or stored; they are gone when you close the tab. Handing a file to the GC log analyser or the memory planner passes it through this browser’s storage (IndexedDB) and the other page deletes it as it reads it.

Runs entirely in your browser; nothing you type leaves this page.