Skip to Content
Course content

174: Thread Dump Analysis

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably been there: the production server is barely using any CPU, the memory looks fine, but the application has completely stopped responding to requests. It's the "silent freeze." In these moments, the instinct for a lot of developers is to just kick the server—restart the JVM and hope the ghost disappears. I've seen people do this for years, and while it "fixes" the immediate outage, it's essentially burning the crime scene before the forensics team arrives. You'll never know why it happened, which means it will happen again.

The "Grep and Pray" Approach

When people first start looking at thread dumps, they usually take a naive approach. They'll run jstack, dump a massive text file to the console, and then use grep -C 5 "BLOCKED" to find any thread that isn't moving. They see a thread blocked on a monitor, they assume that specific line of code is the problem, and they start trying to "optimize" that method.

Here is where that logic fails. In a Java thread dump, the thread that is BLOCKED is almost never the culprit; it's the victim. If Thread A is blocked waiting for a lock held by Thread B, and Thread B is blocked waiting for a lock held by Thread C, greping for "BLOCKED" will show you A and B. But the actual root cause is Thread C, which might be stuck in an infinite loop or waiting on a socket timeout from a legacy API. If you only look at the blocked threads, you're treating the symptom while the disease is sitting comfortably in a different part of the dump.

Tracing the Lock Chain

The professional way to handle this is to stop looking for "blocked" and start looking for "owners." I want you to think of a thread dump as a map of dependencies. Instead of searching for who is waiting, you search for who is holding.

Imagine a scenario where you have a simple bank transfer service. You have two accounts, Account A and Account B. Thread 1 locks Account A and tries to lock Account B. Simultaneously, Thread 2 locks Account B and tries to lock Account A. You now have a classic deadlock. If you just look for the word "BLOCKED," you'll see both threads. But to actually solve this, you need to trace the monitor address.


"Transfer-Thread-1" prio=5 tid=0x001 state=BLOCKED
  at java.lang.Object.wait(Native Method)
  - waiting to lock <0x0000000710a1> (a com.app.Account)
  - locked <0x0000000710b2> (a com.app.Account)

"Transfer-Thread-2" prio=5 tid=0x002 state=BLOCKED
  at java.lang.Object.wait(Native Method)
  - waiting to lock <0x0000000710b2> (a com.app.Account)
  - locked <0x0000000710a1> (a com.app.Account)

When I see this, I don't just see "two blocked threads." I see that Thread 1 is holding 0x710b2 and wants 0x710a1, while Thread 2 is holding 0x710a1 and wants 0x710b2. The "better way" is to map these hex addresses. In a real-world dump with 2,000 threads, this is tedious by hand, which is why I recommend using a tool like fastThread or VisualVM, but the mental model remains the same: follow the lock owner, not the waiter.

The Cost of the Deep Dive

Now, there's a trade-off here. Analyzing a thread dump takes time—sometimes an hour of squinting at hex codes—whereas a restart takes thirty seconds. In the heat of a SEV1 outage, your manager will be screaming for the restart. My advice? Take the dump first. It takes milliseconds to run jstack -l <pid> > dump.txt. Once you have the snapshot, you can restart the server to satisfy the business needs, but you now have the evidence required to actually fix the bug in the next sprint.

If you don't take that dump, you're just gambling that the bug won't return. And in my experience, concurrency bugs are incredibly loyal; they always come back.




📋 Practical Task

Exercise: Hunting the Circular Lock in a Bank Transfer Simulation

Your task is to diagnose a deadlock in a provided Java application. You will be given a program that simulates two threads attempting to transfer money between two accounts in opposite directions, which consistently freezes the application.

Requirements:

  • Run the provided DeadlockSimulation.java.
  • While the program is hanging, use the jstack tool (or the JDK's jcmd) to capture a full thread dump to a file.
  • Analyze the dump to identify the memory addresses (the <0x...> identifiers) of the two monitors causing the deadlock.
  • Write a brief explanation (3-4 sentences) identifying which thread holds which lock and which lock it is waiting for, proving the circular dependency.
  • Modify the code to fix the deadlock (Hint: Consider implementing a consistent locking order, such as sorting the accounts by a unique ID before acquiring locks).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.