Quick answer
A thread dump is a point-in-time map of Java stacks and synchronization state, not a measurement of how much CPU each stack consumed. Capture several bounded samples across the incident interval, align them with CPU, request, lock, JFR, container, and dependency evidence, and look for repeated ownership or progress patterns. Distinguish RUNNABLE work from BLOCKED monitor entry, WAITING or TIMED_WAITING coordination, I/O waits, safepoint delay, virtual-thread pinning, and external capacity limits.
Java 25 jcmd Thread.print provides the traditional dump and lock information. Thread.dump_to_file can write text or JSON and includes platform and virtual threads, but Oracle documents that it omits some information present in traditional dumps. Choose the form that answers the question; do not treat them as interchangeable.
Learning objectives
- Interpret repeated Java 25 thread evidence across platform threads, virtual threads, locks, safepoints, CPU, and downstream waits.
- Choose between traditional thread output, virtual-thread-aware dumps, JFR, and operating-system CPU evidence without overstating any one capture.
Prerequisites
Review Production Java Concurrency Troubleshooting, Java Virtual Threads for Backend Services, and JVM Profiling and JFR.
Evidence and system boundary
Thread state is a JVM classification. RUNNABLE can mean executing Java, executing native work, or ready but not scheduled. WAITING may be intentional coordination. BLOCKED identifies Java monitor entry, not database locks or every form of synchronization. A stack shows where a thread was observed, not how long it stayed there unless another signal supplies duration.
These are explanatory Java 25 examples, not commands executed against a production process:
jcmd <pid> Thread.print -l
jcmd <pid> Thread.dump_to_file -format=json /approved/secure/threads.json
Thread.print can include traditional lock details. Thread.dump_to_file is designed to show virtual threads as well as platform threads, but it omits object addresses, JNI statistics, heap statistics, and other traditional details. Dumps can contain application names, paths, or values encoded in thread names and stack frames, so control access and retention.
Diagnose progress, ownership, and CPU
Take multiple samples far enough apart to show progress but close enough to remain in the same incident phase. Repeated identical stacks can indicate long work, a stuck wait, or a workload that repeatedly reaches the same hot path. Combine them with per-process and per-thread CPU, JFR execution samples, monitor events, request traces, and dependency timing.
For monitor contention, find the owner, blocked population, resource held, and business operation. A large blocked population is secondary if the owner is waiting on a slow dependency while retaining a monitor. Removing synchronization without protecting the invariant can trade latency for data corruption.
Virtual threads reduce the cost of representing blocking tasks, not CPU, connections, memory, or downstream capacity. JFR exposes virtual-thread pinning and submit-failure events. A pinned carrier matters when it constrains useful progress; one event is not automatically an incident. Native calls and foreign functions can pin in Java 25, while monitor behavior differs from older virtual-thread guidance, so use the exact release documentation.
Safepoints coordinate selected JVM operations. A long safepoint log can represent time reaching the safepoint, work at the safepoint, or both. Correlate safepoint logs with CPU scheduling, native code, collector phases, and JFR. Do not assign every application pause to garbage collection.
Production failure scenario
The Spring order service shows high CPU and rising latency after a deployment. One thread dump contains many RUNNABLE JSON serialization stacks; later samples show the same request shape, and JFR plus per-thread CPU confirms sustained execution. GC CPU and pauses remain stable, while the new response mapper creates redundant work.
The team rolls back the mapper in a canary and compares CPU, p99, allocation, safepoint time, GC, and durable order results. It does not enlarge a thread pool: more runnable work would increase competition. Repeated stacks suggested a location; independent CPU evidence supported the hot-path hypothesis.
Common misconceptions
- RUNNABLE does not prove the thread was consuming CPU throughout the interval.
- Many virtual threads do not measure useful concurrency or downstream capacity.
- BLOCKED describes Java monitors, not database row locks or every parked synchronizer.
- One dump cannot prove a deadlock is absent outside the captured JVM boundary.
- A safepoint duration is not automatically a GC pause.
Decision checklist
- Bind captures to one incident phase, JVM, version, instance, and UTC window.
- Take bounded repeated samples and check whether stacks make progress.
- Pair stacks with per-process or per-thread CPU and JFR evidence.
- Find lock owners and protected invariants before changing synchronization.
- Use virtual-thread-aware dumps when task visibility requires them.
- Separate safepoint reach time, safepoint work, and collector pauses.
- Protect dump files as operational data.
- Verify user outcomes, capacity, dependencies, and durable state after mitigation.
Related reading
Continue with JVM JIT, Code Cache, and Warmup Diagnosis. Use the Production Observability & SRE course for signal correlation and the canonical Java Concurrency course plus topic cluster for the complete sequence.
Official sources
- Java 25 jcmd specification
- Java 25 virtual threads
- Java 25 troubleshooting guide
- Java 25 Flight Recorder API guide
Official sources accessed August 19, 2026. Recheck thread-dump capabilities, virtual-thread pinning behavior, and command impact against the exact JDK update.