Heap Dumps and Thread Dumps

Naveen AnamaneniUpdated · 7 min read

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

PointHeap dumpThread dump
ContainsAll objects, fields and referencesAll threads, states, stack traces and locks
AnswersWhat uses the memory? Why isn't it freed?What is each thread doing? Who holds which lock?
FormatBinary .hprof file, often as big as the heapPlain text
CostPauses the JVM while it is writtenVery cheap, a short pause
Typical useMemory leaks, OutOfMemoryErrorHangs, high CPU, deadlocks, slow requests
ToolsEclipse MAT, VisualVM, IntelliJRead directly, or with online analyzers

Taking a Heap Dump

Terminal
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 App

The 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:

SelfHeapDump.java▶ Try it
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);
    }
}
Output
Orders kept: 10000
Dump written: true
Bigger than 1 MB? true

Explanation: 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:

  1. Histogram: objects per class, sorted by size. Find the classes with suspiciously many instances.
  2. Dominator tree: which objects keep the most memory alive. The top entries are often the leak.
  3. 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?".
  4. Retained size vs shallow size: shallow is the object itself; retained is everything that would be freed if it disappeared.

Taking a Thread Dump

Terminal
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):

Terminal output
"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)
PartMeaning
"order-worker"The thread name (why naming threads matters)
cpu=1.99ms elapsed=4.11sCPU time used vs time alive; high CPU points to a busy loop
Thread.StateRUNNABLE, BLOCKED, WAITING or TIMED_WAITING
at ... linesThe 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 BLOCKED on the same lock: one slow thread is holding it.
  • A thread RUNNABLE in 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:

FindDeadlock.java▶ Try it
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());
        }
    }
}
Output
Deadlocked threads: 2
Advitha is BLOCKED, waiting for a lock held by Srihitha
Srihitha is BLOCKED, waiting for a lock held by Advitha

Explanation: 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

  1. Easy: Run any program with -XX:+HeapDumpOnOutOfMemoryError and a small -Xmx, make it run out of memory, and open the dump in VisualVM or Eclipse MAT.
  2. Harder: Change FindDeadlock so it prints each thread's stack trace (info.getStackTrace()), and compare the result with jstack's output for the same program.

Quick check: Heap and thread dumps

4 questions
1Which dump helps find a memory leak?
2Which command prints a thread dump?
3Many threads are BLOCKED on the same lock. What does it suggest?
4Why treat heap dumps as sensitive?

Key Takeaways

Was this tutorial helpful?

Lesson 453 of 482