Generating Thread Dumps from a Sun or IBM JVM Running As A Windows Service

Robert Broeckelmann

DebuggingJVM InternalsLinuxTroubleshootingOperating SystemsJava

I was recently asked if there was a way to generate a Thread Dump from a JVM running as a Windows Service. It occured to me that I had a similar issue during my Masters Project when I was trying to generate a thread dump from a Cygwin xterm; Googling at the time turned up no good way of accomplishing the task. But, I knew that you could use the Java Debugger that ships with the Sun JDK, jdb, to generate a thread dump of all non-System Threads in a JVM. I posed this as a solution to generating Thread Dumps from a JVM running as a Windows Service.

The example given here uses Cygwin to run Unix Shell scripts, but, in theory, the same logic could be run from a DOS Batch Command. I’ll leave the creation of those DOS .COM files to another post.

The same basic idea can be used to grab a Thread Dump from a JVM running as a Windows Service. There are a couple of different comercial and OSS solutions to running JVMs as Windows Services that are outside the scope of this article–check out the references section.

The “Thread Dump” that is generated by this method is not a real Thread Dump. It only captures the non-System Java Threads (with a couple of exceptions as we will see). Likewise, it does not show any lock or memory information–or, anything else that may be associated with a particular vendors JVM thread dumps. But, it will capture a Stack Trace of each thread that is running your application’s code.

This entire method depends upon enabling the JVM Debugging API in your application’s JVM so that a remote debugger can be attached.

For this test, I’m providing a small snippet of code that creates multiple Java threads that spend most of their time in a Thread.sleep() method inside an infinite loop. Here is the code:

 class Thread1 extends Thread {
    private int x;
    public Thread1(ThreadGroup tg) {
        super(tg, "Thread1");
        start();
    }
    public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch(InterruptedException e) {
            e.printStackTrace();
          }
        }
    }
}
class Thread2 extends Thread {
    public Thread2(ThreadGroup tg) {
        super(tg, "Thread2");
        start();
    }
    public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch(InterruptedException e) {
            e.printStackTrace();
          }
        }
    }
}

public class Tester {
    public static void main(String[] args)
    {
        Tester app = new Tester();
    }
    public Tester() {
        ThreadGroup t1tg = new ThreadGroup("ThreadGroup1");
        ThreadGroup t2tg = new ThreadGroup("ThreadGroup2");
        Thread1 t11 = new Thread1(t1tg);
        Thread1 t12 = new Thread1(t1tg);
        Thread2 t21 = new Thread2(t2tg);
        Thread2 t22 = new Thread2(t2tg);
        try {
          t11.join();
          t12.join();
          t21.join();
          t22.join();
        } catch (Exception e) {
          e.printStackTrace();
        }
    }
}

This code was written with Sun Java 1.5.0_15 running on Windows Vista.

To run this program something like the following can be used:

java Tester

To setup my environment to run this program, inside of a Cygwin xterm, I use the following script:

 JAVA_HOME=/cygdrive/c/Progra~1/Java/jdk1.5.0_15
PATH=$JAVA_HOME/bin:$PATH
CLASSPATH=.
export JAVA_HOME PATH CLASSPATH

This program doesn’t do anything more than spin up four Java Threads and put them into infinite loops, sleeping for most of it.

We need to run this program in a JVM that has remote debugging enabled. To do this, run the Java command with the following options:

 #!/bin/bash
export CLASSPATH=.
java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5002 Tester &

This will start the JVM with the remote debugging API enabled. It is listening on port 5002

We can now use a scripted jdb session to generate a thread dump by running the following script:

 #!/bin/bash
echo -e "suspend\nwhere all\nresume\nquit" | "$JAVA_HOME"/bin/jdb -connect \
  com.sun.jdi.SocketAttach:hostname=localhost,port=5002

When this script is run, something similar to the following will be generated against a Sun JVM:

 $ ./dump.sh
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> All threads suspended.
> Thread2:
  [1] java.lang.Thread.sleep (native method)
  [2] Thread2.run (Tester.java:26)
Thread2:
  [1] java.lang.Thread.sleep (native method)
  [2] Thread2.run (Tester.java:26)
Thread1:
  [1] java.lang.Thread.sleep (native method)
  [2] Thread1.run (Tester.java:10)
Thread1:
  [1] java.lang.Thread.sleep (native method)
  [2] Thread1.run (Tester.java:10)
Signal Dispatcher:
Finalizer:
  [1] java.lang.Object.wait (native method)
  [2] java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:120)
  [3] java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:136)
  [4] java.lang.ref.Finalizer$FinalizerThread.run (Finalizer.java:159)
Reference Handler:
  [1] java.lang.Object.wait (native method)
  [2] java.lang.Object.wait (Object.java:474)
  [3] java.lang.ref.Reference$ReferenceHandler.run (Reference.java:116)
main:
  [1] java.lang.Object.wait (native method)
  [2] java.lang.Thread.join (Thread.java:1,095)
  [3] java.lang.Thread.join (Thread.java:1,148)
  [4] Tester.<init> (Tester.java:48)
  [5] Tester.main (Tester.java:38)
> All threads resumed.
>

You can see the jdb debugger starting up, suspending all threads, generating a stack trace for each Application Thread, resuming threads, and exiting.

You can see the two Thread1 object’s threads and two Thread2 object’s threads that were created in the Tester.main() method. Likewise, you can see the Java Thread running the main() method, which is waiting for the other four threads it created to end.

In a similar fashion, if a java process running as a Windows Service were started with the same options, the dump.sh script would also be able to generate a thread dump as demonstrated here.

References:

[1] http://java.sun.com

[2] http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/jdb.html

Read next: