Heap Dumps and Thread Dumps
When a Java program misbehaves in production, two snapshots answer most questions. A heap dump is a file containing every object in the heap at one moment, with its fields and references; it shows what is using memory. A thread dump is a text listing of every thread, its state and its stack trace; it shows what the program is doing right now, and it is the main tool for hangs, slowness and deadlocks. In this lesson, you will learn how to take both, read them, and even take them from inside a program.
Heap Dump vs Thread Dump
| Point | Heap dump | Thread dump |
|---|---|---|
| Contains | All objects, fields and references | All threads, states, stack traces and locks |
| Answers | What uses the memory? Why isn't it freed? | What is each thread doing? Who holds which lock? |
| Format | Binary .hprof file, often as big as the heap | Plain text |
| Cost | Pauses the JVM while it is written | Very cheap, a short pause |
| Typical use | Memory leaks, OutOfMemoryError | Hangs, high CPU, deadlocks, slow requests |
| Tools | Eclipse MAT, VisualVM, IntelliJ | Read directly, or with online analyzers |
Taking a Heap Dump
jcmd <pid> GC.heap_dump /var/dumps/app.hprof
jmap -dump:live,format=b,file=/var/dumps/app.hprof <pid>
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dumps AppThe first two take a dump of a running process. The third saves one automatically when an OutOfMemoryError happens, which is the most useful setting for production.
Example: A Heap Dump from Inside the Program
The HotSpotDiagnosticMXBean lets a program dump its own heap, which is how some admin pages offer a "download heap dump" button:
import com.sun.management.HotSpotDiagnosticMXBean;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class SelfHeapDump {
record Order(int id, String customer) { }
public static void main(String[] args) throws Exception {
List<Order> orders = new ArrayList<>();
for (int i = 0; i < 10_000; i++) {
orders.add(new Order(i, "Navika Anamaneni"));
}
HotSpotDiagnosticMXBean bean = ManagementFactory
.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
Path file = Path.of("orders.hprof");
Files.deleteIfExists(file);
bean.dumpHeap(file.toString(), true);
long size = Files.size(file);
System.out.println("Orders kept: " + orders.size());
System.out.println("Dump written: " + Files.exists(file));
System.out.println("Bigger than 1 MB? " + (size > 1_000_000));
Files.delete(file);
}
}Orders kept: 10000
Dump written: true
Bigger than 1 MB? trueExplanation: dumpHeap(path, true) writes an .hprof file; true means live objects only, so the JVM runs a full GC first and the dump contains only reachable objects. The file name must end in .hprof. Opened in Eclipse MAT, this dump would show 10,000 Order objects held by an ArrayList.
Reading a Heap Dump
In Eclipse MAT or VisualVM, look at:
- Histogram: objects per class, sorted by size. Find the classes with suspiciously many instances.
- Dominator tree: which objects keep the most memory alive. The top entries are often the leak.
- Path to GC roots: for a suspicious object, the chain of references that keeps it reachable. This is the answer to "why isn't it freed?".
- Retained size vs shallow size: shallow is the object itself; retained is everything that would be freed if it disappeared.
Taking a Thread Dump
jstack <pid>
jcmd <pid> Thread.print
kill -3 <pid>kill -3 (on Linux and macOS) makes the JVM print a thread dump to its own standard output; it doesn't stop the program. On Windows, pressing Ctrl+Break in the console does the same.
Reading a Thread Dump
Each thread entry looks like this (from a real dump):
"order-worker" #25 [27395] prio=5 os_prio=31 cpu=1.99ms elapsed=4.11s ... waiting on condition
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleepNanos0(java.base@25.0.5.0.3/Native Method)
at java.lang.Thread.sleep(java.base@25.0.5.0.3/Thread.java:540)| Part | Meaning |
|---|---|
"order-worker" | The thread name (why naming threads matters) |
cpu=1.99ms elapsed=4.11s | CPU time used vs time alive; high CPU points to a busy loop |
Thread.State | RUNNABLE, BLOCKED, WAITING or TIMED_WAITING |
at ... lines | The stack trace, most recent call first |
- locked <0x...> | A monitor this thread holds |
- waiting to lock <0x...> | A monitor it wants; it is BLOCKED |
What to look for:
- Many threads
BLOCKEDon the same lock: one slow thread is holding it. - A thread
RUNNABLEin the same method in every dump: a busy loop or slow computation. - "Found one Java-level deadlock": the JVM detected a deadlock and lists the threads involved.
Example: Detecting a Deadlock from Inside the Program
The ThreadMXBean can find deadlocks and report the stack and lock information of each thread, the same data a thread dump shows:
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Arrays;
import java.util.Comparator;
public class FindDeadlock {
static final Object ACCOUNT_A = new Object();
static final Object ACCOUNT_B = new Object();
static void transfer(Object first, Object second) {
synchronized (first) {
sleep(200);
synchronized (second) {
System.out.println("transfer done");
}
}
}
static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(
() -> transfer(ACCOUNT_A, ACCOUNT_B), "Srihitha");
Thread t2 = new Thread(
() -> transfer(ACCOUNT_B, ACCOUNT_A), "Advitha");
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
sleep(1000);
ThreadMXBean mx = ManagementFactory.getThreadMXBean();
long[] ids = mx.findDeadlockedThreads();
System.out.println("Deadlocked threads: " + ids.length);
ThreadInfo[] infos = mx.getThreadInfo(ids);
Arrays.sort(infos, Comparator.comparing(ThreadInfo::getThreadName));
for (ThreadInfo info : infos) {
System.out.println(info.getThreadName() + " is "
+ info.getThreadState() + ", waiting for a lock held by "
+ info.getLockOwnerName());
}
}
}Deadlocked threads: 2
Advitha is BLOCKED, waiting for a lock held by Srihitha
Srihitha is BLOCKED, waiting for a lock held by AdvithaExplanation: Each thread locks one account and then waits for the other, so neither can continue (see "Deadlock in Java"). findDeadlockedThreads() returns the IDs of the stuck threads, and ThreadInfo tells us each one's state (sorted by name, because the order of the IDs isn't fixed) and who owns the lock it needs. The threads are daemons so the program can still exit. jstack prints this same information under "Found one Java-level deadlock".
Common Mistakes
Taking a heap dump on a busy production server without warning. It pauses the JVM and writes a file as large as the heap; make sure there is disk space and the pause is acceptable.
Sharing heap dumps carelessly. They contain every object, including passwords, tokens and personal data. Treat them as sensitive.
Reading only one thread dump. One snapshot can't tell stuck from busy.
Interview Corner
What is the difference between a heap dump and a thread dump?
A heap dump is a binary snapshot of all objects in the heap with their references, used to find memory leaks and the cause of OutOfMemoryError; it is taken with jcmd GC.heap_dump, jmap -dump or automatically with -XX:+HeapDumpOnOutOfMemoryError, and analyzed with tools such as Eclipse MAT. A thread dump is a text snapshot of every thread's name, state, stack trace and held or awaited locks, used to diagnose hangs, high CPU and deadlocks; it is taken with jstack, jcmd Thread.print or kill -3. Several thread dumps a few seconds apart show which threads are really stuck.
How do you find a deadlock in a running application?
Take a thread dump with jstack or jcmd Thread.print. The JVM detects monitor and ReentrantLock deadlocks and prints "Found one Java-level deadlock" with the threads, the locks each holds and the locks each waits for. Programmatically, ThreadMXBean.findDeadlockedThreads() returns the same information.
Practice
- Easy: Run any program with
-XX:+HeapDumpOnOutOfMemoryErrorand a small-Xmx, make it run out of memory, and open the dump in VisualVM or Eclipse MAT. - Harder: Change
FindDeadlockso it prints each thread's stack trace (info.getStackTrace()), and compare the result withjstack's output for the same program.
Key Takeaways
Was this tutorial helpful?