|
The Java Specialists' Newsletter
Issue 093 2004-07-28
Category:
Concurrency
Java version: Sun JDK 1.5.0-beta2 Automatically Detecting Thread Deadlocksby Dr. Heinz M. KabutzAbstract: Deadlocks between synchronized monitors are impossible to resolve in Java. In this newsletter we look at a new MXBean that can detect deadlocks between threads and use it to warn us.
Welcome to the 93rd edition of The Java(tm) Specialists' Newsletter. I posted a link to
our
last newsletter to
The
Serverside. Didn't get too many comments (maybe not
controversial enough ?!) but at least it made the front
page for a few days :-)
Here is a really funny animated cartoon about
what
Sun Microsystems could do with their $2,000,000,000 [Sorry, it is not available anymore].
I was teaching last week about Swing, and coincidentally a friend sent me
a
hilarious animated cartoon about GridBagLayout. The
nicest approach to GUI development that I have seen is in
IntelliJ IDEA. They separate the layout from the rest of the
GUI, and inject bytecode into your classes, instead of
generating loads of source code. Once you get the hang of
IDEA's GUI editor tool, you can smack together a
GridBagLayout GUI in seconds, rather than minutes or days.
Thanks for reading this newsletter on our website. We also have a mailing list. That is where the real action takes place (webinars, free reports, etc.). Maybe subscribe today?
Advanced Java Courses on Crete:Java Specialists Master Course 18-21 June 2013 and
Concurrency Specialists Course 6-9 August 2013.
Automatically Detecting Thread Deadlocks
After our last newsletter, Kris Schneider mentioned that he
had seen the Permanent Generation run out of space. He has
kindly posted his improved version of my MemoryWarningSystem on
The
Serverside.
Scott Sobel brought up the issue of OOME occuring when too
many threads were created. The problem with too many threads
is that they need stack space. Both Scott and I have
experienced where increasing the maximum heap space actually
decreased the number of threads that we could create
before getting an OOME. Unfortunately this maximum number
of threads seems to be kind-of magical, and depends on the
operating system and on the initial stack size per thread.
This brought me to this new warning system, that notifies me
if we have too many threads. In
order to not get too many notifications, I take the approach
that you get
one warning when we pass the thread
count threshold. If you slip below the threshold, and go
above it again, you will get another warning notification.
This is the same approach taken by the memory bean. Better
would probably be to have a high- and low-water mark.
In addition, it can also tell if there are deadlocked threads.
My
very first Java newsletter, sent in 2000 to some friends
and colleagues gleaned from my contact list, demonstrated how
you could find thread deadlocks by hand. This system finds
them automatically for you. Seems we have progressed in the
last 4 years! Looking for deadlocked threads is potentially
slow, so this code could affect your performance. However,
I would rather have a slower correct program than a
lightning fast incorrect program.
There is absolutely nothing you can do with a deadlocked
thread. You cannot stop it, you cannot interrupt it,
you cannot tell it to stop trying to get a lock, and you
also cannot tell it to let go of the locks that it owns.
This is one of the criticism in
Doug
Lea's book about the primitive monitor-based locking
mechanisms. Once you try to get a lock, you will forever try
and never give up.
The concurrency handling mechanisms of Doug's book are now
in the java.util.concurrent package of JDK 1.5.
This brought me to the question, what is the definition of a
deadlock? In Webopedia.com, they describe it nicely:
A condition that occurs when two processes are each waiting
for the other to complete before proceeding. The result is
that both processes hang. Deadlocks occur most commonly in
multitasking and client/server environments. Ideally, the
programs that are deadlocked, or the operating system,
should resolve the deadlock, but this doesn't always
happen. A deadlock is also called a deadly embrace.
(Source
Webopedia.com)
Enough theory, here is the code to the ThreadWarningSystem,
that detects when there are too many threads, and finds thread
deadlocks:
import java.lang.management.*;
import java.util.*;
public class ThreadWarningSystem {
private final Timer threadCheck = new Timer("Thread Monitor", true);
private final ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
private final Collection<Listener> listeners = new ArrayList<Listener>();
/**
* The number of milliseconds between checking for deadlocks.
* It may be expensive to check for deadlocks, and it is not
* critical to know so quickly.
*/
private static final int DEADLOCK_CHECK_PERIOD = 500;
/**
* The number of milliseconds between checking number of
* threads. Since threads can be created very quickly, we need
* to check this frequently.
*/
private static final int THREAD_NUMBER_CHECK_PERIOD = 20;
private static final int MAX_STACK_DEPTH = 30;
private boolean threadThresholdNotified = false;
private Set deadlockedThreads = new HashSet();
/**
* Monitor only deadlocks.
*/
public ThreadWarningSystem() {
threadCheck.schedule(new TimerTask() {
public void run() {
long[] ids = mbean.findMonitorDeadlockedThreads();
if (ids != null && ids.length > 0) {
for (Long l : ids) {
if (!deadlockedThreads.contains(l)) {
deadlockedThreads.add(l);
ThreadInfo ti = mbean.getThreadInfo(l, MAX_STACK_DEPTH);
fireDeadlockDetected(ti);
}
}
}
}
}, 10, DEADLOCK_CHECK_PERIOD);
}
/**
* Monitor deadlocks and the number of threads.
*/
public ThreadWarningSystem(final int threadThreshold) {
this();
threadCheck.schedule(new TimerTask() {
public void run() {
if (mbean.getThreadCount() > threadThreshold) {
if (!threadThresholdNotified) {
fireThresholdExceeded();
threadThresholdNotified = true;
}
} else {
threadThresholdNotified = false;
}
}
}, 10, THREAD_NUMBER_CHECK_PERIOD);
}
private void fireDeadlockDetected(ThreadInfo thread) {
// In general I avoid using synchronized. The surrounding
// code should usually be responsible for being threadsafe.
// However, in this case, the timer could be notifying at
// the same time as someone is adding a listener, and there
// is nothing the calling code can do to prevent that from
// occurring. Another tip though is this: when I synchronize
// I use a private field to synchronize on, instead of
// "this".
synchronized (listeners) {
for (Listener l : listeners) {
l.deadlockDetected(thread);
}
}
}
private void fireThresholdExceeded() {
ThreadInfo[] allThreads = mbean.getThreadInfo(mbean.getAllThreadIds());
synchronized (listeners) {
for (Listener l : listeners) {
l.thresholdExceeded(allThreads);
}
}
}
public boolean addListener(Listener l) {
synchronized (listeners) {
return listeners.add(l);
}
}
public boolean removeListener(Listener l) {
synchronized (listeners) {
return listeners.remove(l);
}
}
/**
* This is called whenever a problem with threads is detected.
* The two events are deadlockDetected() and thresholdExceeded().
*/
public interface Listener {
/**
* @param deadlockedThread The deadlocked thread, with stack
* trace of limited depth.
*/
void deadlockDetected(ThreadInfo deadlockedThread);
/**
* @param allThreads All the threads in the JVM, without
* stack traces.
*/
void thresholdExceeded(ThreadInfo[] allThreads);
}
}
The following test code creates many threads, then waits a
few seconds before creating a whole new batch. We will see
the warning system go off when we exceed the treshhold for
the first time, then again after we have waited for the
first batch of threads to die.
import java.lang.management.*;
public class TooManyThreadsTest {
public static void main(String[] args) throws InterruptedException {
ThreadWarningSystem tws = new ThreadWarningSystem(500);
tws.addListener(new ThreadWarningSystem.Listener() {
public void deadlockDetected(ThreadInfo thread) { }
public void thresholdExceeded(ThreadInfo[] threads) {
System.out.println("Threshold Exceeded");
System.out.println("threads.length = " + threads.length);
}
});
createBatchOfThreads();
Thread.sleep(10000);
System.out.println("We should've dipped below the threshold");
createBatchOfThreads();
}
private static void createBatchOfThreads() {
for (int i=0; i<1000; i++) {
new Thread() { {start();}
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) { }
}
};
}
}
}
On my machine I get the following output:
Threshold Exceeded
threads.length = 571
We should've dipped below the threshold
Threshold Exceeded
threads.length = 507
Testing for deadlocks is even more interesting. I have the
classic case where two threads want lock1 and lock2, but in
opposite orders. Then I have a more difficult case, where we
have three threads that lock each other out:
import java.lang.management.ThreadInfo;
public class DeadlockedThreadsTest {
public static void main(String[] args) {
ThreadWarningSystem tws = new ThreadWarningSystem();
tws.addListener(new ThreadWarningSystem.Listener() {
public void deadlockDetected(ThreadInfo inf) {
System.out.println("Deadlocked Thread:");
System.out.println("------------------");
System.out.println(inf);
for (StackTraceElement ste : inf.getStackTrace()) {
System.out.println("\t" + ste);
}
}
public void thresholdExceeded(ThreadInfo[] threads) { }
});
// deadlock with three locks
Object lock1 = new String("lock1");
Object lock2 = new String("lock2");
Object lock3 = new String("lock3");
new DeadlockingThread("t1", lock1, lock2);
new DeadlockingThread("t2", lock2, lock3);
new DeadlockingThread("t3", lock3, lock1);
// deadlock with two locks
Object lock4 = new String("lock4");
Object lock5 = new String("lock5");
new DeadlockingThread("t4", lock4, lock5);
new DeadlockingThread("t5", lock5, lock4);
}
// There is absolutely nothing you can do when you have
// deadlocked threads. You cannot stop them, you cannot
// interrupt them, you cannot tell them to stop trying to
// get a lock, and you also cannot tell them to let go of
// the locks that they own.
private static class DeadlockingThread extends Thread {
private final Object lock1;
private final Object lock2;
public DeadlockingThread(String name, Object lock1, Object lock2) {
super(name);
this.lock1 = lock1;
this.lock2 = lock2;
start();
}
public void run() {
while (true) {
f();
}
}
private void f() {
synchronized (lock1) {
g();
}
}
private void g() {
synchronized (lock2) {
// do some work...
for (int i = 0; i < 1000 * 1000; i++) ;
}
}
}
}
Not surprisingly, it takes longer for the deadlock to happen
with the three threads than with the two threads. Here is
the output from the program:
Deadlocked Thread:
------------------
Thread t5 (Id = 13) BLOCKED java.lang.String@de6ced
DeadlockedThreadsTest$DeadlockingThread.g(DeadlockedThreadsTest.java:65)
DeadlockedThreadsTest$DeadlockingThread.f(DeadlockedThreadsTest.java:61)
DeadlockedThreadsTest$DeadlockingThread.run(DeadlockedThreadsTest.java:56)
Deadlocked Thread:
------------------
Thread t4 (Id = 12) RUNNABLE null
DeadlockedThreadsTest$DeadlockingThread.g(DeadlockedThreadsTest.java:65)
DeadlockedThreadsTest$DeadlockingThread.f(DeadlockedThreadsTest.java:61)
DeadlockedThreadsTest$DeadlockingThread.run(DeadlockedThreadsTest.java:56)
Deadlocked Thread:
------------------
Thread t3 (Id = 11) BLOCKED java.lang.String@c17164
DeadlockedThreadsTest$DeadlockingThread.g(DeadlockedThreadsTest.java:65)
DeadlockedThreadsTest$DeadlockingThread.f(DeadlockedThreadsTest.java:61)
DeadlockedThreadsTest$DeadlockingThread.run(DeadlockedThreadsTest.java:56)
Deadlocked Thread:
------------------
Thread t1 (Id = 9) BLOCKED java.lang.String@1fb8ee3
DeadlockedThreadsTest$DeadlockingThread.g(DeadlockedThreadsTest.java:65)
DeadlockedThreadsTest$DeadlockingThread.f(DeadlockedThreadsTest.java:61)
DeadlockedThreadsTest$DeadlockingThread.run(DeadlockedThreadsTest.java:56)
Deadlocked Thread:
------------------
Thread t2 (Id = 10) RUNNABLE null
DeadlockedThreadsTest$DeadlockingThread.g(DeadlockedThreadsTest.java:65)
DeadlockedThreadsTest$DeadlockingThread.f(DeadlockedThreadsTest.java:61)
DeadlockedThreadsTest$DeadlockingThread.run(DeadlockedThreadsTest.java:56)
You can get back to the original Thread from the ThreadInfo
class if necessary by calling Thread.getAllStackTraces(). I
would presume that this is an expensive operation, so use it
with caution. The Thread's ID matches the ThreadInfo's ID,
so we can always get back to the Thread from the ThreadInfo.
private static Thread findMatchingThread(ThreadInfo inf) {
Map<Thread, StackTraceElement[]> all = Thread.getAllStackTraces();
for (Map.Entry<Thread, StackTraceElement[]> entry : all.entrySet()) {
if (entry.getKey().getId() == inf.getThreadId()) {
return entry.getKey();
}
}
throw new NoSuchElementException();
}
Apparently, JDK 1.5 beta 3 has been released. Exciting stuff
and I can hardly wait for it to be properly released, so that
I can start slowly bringing this into some products. In the
past, I would say to customers: "If the application stops
responding, please go to the console and press CTRL+Break and
then email me the threads that are printed on the screen."
Now we can get notified automatically. Ohhhh, what joy!
Even though the actual code of this newsletter was easy, it
took a long time to write this newsletter, and I don't know
why. Maybe I have been too distracted of late. In Germany,
I went to visit my good friend Dr Jung, who wrote the
masterpiece
on dynamic proxies. We were a bit late for the train,
so Christoph drove at breakneck speed to the train station,
then grabbing my luggage ran full steam and hopped onto the
train. A few minutes later, Christoph appeared and informed
me that I had gotten onto the wrong train! Perhaps I am
getting older, or maybe I am just becoming more nutty :-)
Kind regards
Heinz
Concurrency Articles
Related Java Course
Discuss at The Java Specialist Club
|