We use essential cookies for login and local storage for your preferences. For usage metrics, we use Google Analytics 4 — you can decline without losing anything in the product. Learn more

ThreadMineThreadMine
Back

Guia técnico

Virtual thread dump analyzer for Java (Project Loom)

Paste the output of jcmd Thread.dump_to_file and see which virtual threads are pinned, which carriers hold them, and what the rest are waiting on. jstack does not list them.

Analyze a dump now

Quick answer

A virtual thread dump analyzer reads the output of jcmd <pid> Thread.dump_to_file and tells you what your virtual threads are doing: which are pinned to a carrier, which are mounted, unmounted or waiting. A regular jstack does not list them.

Generate the dump with the command below, then paste it or upload the file on the analyze page. No signup for the first dump.

jcmd <pid> Thread.dump_to_file -format=json /tmp/vthreads.json

How to take a thread dump with virtual threads

The classic tools, jstack <pid> and jcmd <pid> Thread.print, print the platform threads. A service that runs on virtual threads can have hundreds of thousands of them, and they do not come out of those commands as threads of their own. From JDK 21, Thread.dump_to_file writes a dump that includes them, in one of two formats:

# Plain text, same layout family as jstack:
jcmd <pid> Thread.dump_to_file -format=text /tmp/vthreads.txt

# JSON, with thread containers and, on JDK 25, state and carrier:
jcmd <pid> Thread.dump_to_file -format=json /tmp/vthreads.json

The file is written by the target JVM, so the path must be writable by that process, not by the user who runs jcmd. For the other ways to capture a dump (kill -3, containers, Windows), see how to take a thread dump.

What ThreadMine shows for virtual threads

  • Pinning, from the real signal. A virtual thread is reported as pinned when its stack has the frame java.lang.VirtualThread.parkOnCarrierThread. No guessing from thread names or markers. One or two pinned virtual threads are a warning; three or more are critical. The finding lists the carriers involved when the dump names them. Why it happens and how to fix it: virtual thread pinning.
  • Mounted, unmounted and waiting. The Virtual Threads panel counts the total and splits it into Mounted, Unmounted, Waiting and Pinned.
  • Grouped by carrier. When the dump says which carrier runs each virtual thread, a table groups them per carrier, with the pinned ones first. Click a carrier to list its virtual threads.
  • Identity by thread id. Virtual threads usually have no name, so an unnamed one appears as virtual #<tid> instead of being merged with the others.
  • Large dumps. The text and the JSON are parsed thread by thread, and dumps with more than 500,000 virtual threads are analyzed as a sample, with a notice in the report. The counts by state still cover all of them.

Text or JSON?

Both are accepted, pasted or as a file (.txt, .log, .json, or a .zip / .gz with one of them inside). The JSON describes the dump as thread containers, each with its threads, and carries more structure:

  1. JDK 25: each virtual thread comes with its state and its carrier, so the carrier table is exact.
  2. JDK 22 to 24: the JSON has no state, virtual flag or carrier. ThreadMine derives the state from the top of the stack and recognises a virtual thread by the bottom of its stack.
  3. Containers: the JSON lists the container of each thread (executor, ForkJoinPool, the root container). ThreadMine parses them into the analysis data, but the report does not chart them.

If you have no preference, use JSON on JDK 25 and text on the older JDKs. Dump with sensitive data? Mask it locally first.

Common findings

Pinning in synchronized or Object.wait (JDK 23 and earlier)

A virtual thread that blocks inside a synchronized block, or in Object.wait(), keeps its carrier until JDK 23. In the dump it shows up with the parkOnCarrierThread frame, and the pinned counter turns amber. JDK 24 (JEP 491) removed that case; native and foreign-function frames can still pin.

Carrier starvation

The default carrier pool has about one thread per CPU core. When, in the carrier table, every carrier has pinned virtual threads and the application stops answering, the scheduler has nothing left to run the rest on. It looks like a deadlock but is not a lock cycle, so the carrier table is the place to check.

A virtual thread stuck in I/O

One dump shows a virtual thread waiting on a socket, not for how long. To tell stuck from merely busy, take two dumps a few seconds apart and compare which virtual threads sit on the same frame in both. The thread dump analysis guide shows how to read that.

More about the general reading of a dump: Java thread dump analyzer.

Frequently asked questions

Does jstack show virtual threads?

No. jstack and jcmd Thread.print list platform threads; virtual threads are not enumerated as threads of their own. To get them, use jcmd <pid> Thread.dump_to_file, available from JDK 21, in text or JSON format.

Which command takes a virtual thread dump?

jcmd <pid> Thread.dump_to_file -format=text <file> or jcmd <pid> Thread.dump_to_file -format=json <file>. ThreadMine reads both. Paste the file content or upload it on the analyze page.

How does ThreadMine decide that a virtual thread is pinned?

By the frame java.lang.VirtualThread.parkOnCarrierThread in its stack, the frame the JVM shows when a virtual thread parks while still holding its carrier. If that frame is not in the dump, no pinning is reported. One or two pinned virtual threads raise a warning; three or more raise a critical finding.

Why are my virtual threads unnamed?

Virtual threads are created without a name unless your code gives them one, so the dump shows an empty name. ThreadMine identifies an unnamed virtual thread by its thread id and shows it as "virtual #<tid>", so unnamed threads are not merged into a single entry.

Text or JSON: which format should I use?

Either works. JSON also lists the thread containers (executors, ForkJoinPools) and, on JDK 25, the state and carrier of every virtual thread. On JDK 22 to 24 the JSON has no state or carrier field, and ThreadMine derives the state from the top of each stack.

How many virtual threads can I analyze?

Up to 500,000 virtual threads keep their full stack and locks. Beyond that the dump is analyzed as a sample: the totals per state still cover every virtual thread, but only the first 500,000 keep their detail, and the report says so. The upload size limit shown in the upload box also applies.

Can a single dump show a virtual thread stuck in I/O?

Not reliably. A dump is one instant: it shows a virtual thread waiting, not for how long. Take two dumps a few seconds apart and see which virtual threads are in the same place in both.

Got a virtual thread dump?

Paste it and see the pinned virtual threads and the carriers holding them. No signup.