Which JVM tools do you reach for when diagnosing a real performance problem, and what is each one for?
There are four tools worth knowing, and they answer four different questions.
Thread dumps answer where is it stuck. The management API gives you a snapshot of every live thread with its stack trace, its state and the lock it holds or wants. It is the tool for a hung application, a suspected deadlock and a slow request. Heap dumps answer what is using the memory. A dump is a snapshot of every object in the heap, and a heap analyser turns it into a dominator tree showing what retains the most memory. That is how you find the object behind a leak.
Flight recorder answers what happened over time. It is a low-overhead event recorder built into the JVM that captures allocation, locking, IO and method sampling, and it can produce a profile rather than a single snapshot. For a performance regression it is usually the best first tool, because it shows you the shape of the problem rather than one moment of it.
And the management API answers what do the counters say. Garbage collector counts and times, memory pool usage, thread counts and compilation statistics, all readable from inside the program.
Two operational notes. The command-line diagnostics — the process inspection and dump tools — work on a running process without changing any code, which is why they are what you reach for in production. And a thread dump is often enough on its own: the interesting part is the same stack repeated across several threads, which points straight at the bottleneck.
Here is the same thing as runnable code:
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
public class Main {
public static void main(String[] args) {
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
System.out.println("thread count : " + threads.getThreadCount());
System.out.println("peak count : " + threads.getPeakThreadCount());
System.out.println("daemon count : " + threads.getDaemonThreadCount());
System.out.println("total started : " + threads.getTotalStartedThreadCount());
System.out.println("current thread : " + Thread.currentThread().getName());
System.out.println("thread state : " + Thread.currentThread().getState());
System.out.println("stack depth : "
+ threads.getThreadInfo(Thread.currentThread().getId()).getStackTrace().length);
}
}