Some time back I posted some memory info utilities. Since then I’ve had it in the back of my head to try and get a programmatic dump of the thread stack traces like you can get with Ctrl-Break (on Windows) or Ctrl-\ (on Linux) or kill -3 pid. (SIGQUIT)
These are useful to seeing why programs are hanging and this particular thread dump will also tell you information about deadlocks. It’s been a critical tool for me since it was introduced oh so many years ago.
However, in many cases it is inconvenient to manually trigger such a dump. For one, I’ve never been able to figure out how to do from the SDK and for another, when running as a launch4j executable you don’t even have the console anymore. The biggest thing is that I wanted something that users could trigger with a special hot-key combination right from inside the game.
In the midst of some threading issue, it’s nice to collect as much data as possible before you kill the program for real and lose all of that once in a lifetime state.
…and so I finally got off my rear and figured this out. The results are this ThreadUtils class.
[java]
package util;
import java.lang.management.;
import java.util.;
public class ThreadUtils {
public static void safeSleep( long ms ) {
try {
Thread.sleep(ms);
} catch( InterruptedException e ) {
throw new RuntimeException( “Thread interrupted”, e );
}
}
public static String getThreadDumpString() {
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
StringBuilder sb = new StringBuilder();
sb.append( String.format("Thread dump at: %1$tF %1$tT", new java.util.Date()) );
sb.append( "\n\n" );
long[] deadLockedArray = threads.findDeadlockedThreads();
Set<Long> deadlocks = new HashSet<Long>();
if( deadLockedArray != null ) {
for( long i : deadLockedArray ) {
deadlocks.add(i);
}
}
// Build a TID map for looking up more specific thread
// information than provided by ThreadInfo
Map<Long, Thread> threadMap = new HashMap<Long, Thread>();
for( Thread t : Thread.getAllStackTraces().keySet() ) {
threadMap.put( t.getId(), t );
}
// This only works in 1.6+... but I think that's ok
ThreadInfo[] infos = threads.dumpAllThreads(true, true);
for( ThreadInfo info : infos ) {
StringBuilder threadMetaData = new StringBuilder();
Thread thread = threadMap.get(info.getThreadId());
if( thread != null ) {
threadMetaData.append("(");
threadMetaData.append(thread.isDaemon() ? "daemon " : "");
threadMetaData.append(thread.isInterrupted() ? "interrupted " : "");
threadMetaData.append("prio=" + thread.getPriority());
threadMetaData.append(")");
}
String s = info.toString().trim();
// Inject the meta-data after the ID... Presumes a
// certain format but it's low priority information.
s = s.replaceFirst( "Id=\\d+", "$0 " + threadMetaData );
sb.append( s );
sb.append( "\n" );
if( deadlocks.contains(info.getThreadId()) ) {
sb.append( " ** Deadlocked **" );
sb.append( "\n" );
}
sb.append( "\n" );
}
return sb.toString();
}
public static void dumpThreadInfo() {
System.out.println( getThreadDumpString() );
}
}
[/java]
On Oracle’s JDK, this prints a nice set of info. Apparently the ThreadInfo.toString() is implementation specific so the output may suck on other non-Oracle JDKs… we’ll just have to see, I guess. In the standard JDK it’s nice because it smartly combines a lot of info in a nice readable form that would take a bunch of logic to duplicate.
As a test, you can force a deadlock and then dump the threads:
[java]
public static void main( String… args ) throws Exception {
final Object obj1 = new Object();
final Object obj2 = new Object();
Thread test1 = new Thread( “Test1” ) {
public void run() {
synchronized( obj1 ) {
ThreadUtils.safeSleep(1000);
synchronized( obj2 ) {
while( true ) {
ThreadUtils.safeSleep(10);
}
}
}
}
};
Thread test2 = new Thread( "Test2" ) {
public void run() {
synchronized( obj2 ) {
ThreadUtils.safeSleep(1000);
synchronized( obj1 ) {
while( true ) {
ThreadUtils.safeSleep(10);
}
}
}
}
};
test1.start();
test2.start();
Thread.sleep(2000);
ThreadUtils.dumpThreadInfo();
System.exit(0);
}
[/java]
Maybe someone finds it useful. For tracking down random or rare thread/lock/resource deadlocks it can be a life saver to get this information.
Edit: note that because of the specific method I use on the mbean, this is Java 1.6 or higher only.