How to print Java Stack trace and analyze thread states with example

The Java stack trace is a great debugging tool to analyze the state of the Threads in your Java Application at any given time. You can use the stack trace to detect deadlock in your application or why your application is taking more time to process some events then it should. In this post we will see how you can print the stack trace of your Java application and how to interpret it. The example code used in this tutorial is hosted at Github.

Print Stack Trace in Java

While you are debugging your Java application, you can enable an endpoint on your web server to return the stack trace whenever you hit the endpoint by using the method below.

public static void printStackTrace() {
// Get all threads in Java.
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
// Print the thread name and current state of thread.
System.out.println("Thread Name:" + thread.getName());
System.out.println("Thread State:" + thread.getState());
// Get the stack trace for the thread and print it.
StackTraceElement[] stackTraceElements = thread.getStackTrace();
for (StackTraceElement stackTraceElement : stackTraceElements) {
System.out.println("\t" + stackTraceElement);
}
System.out.println("\n");
}
}

The above example would print the stack trace of all the threads present in the Java Process allowing you to monitor your thread state in real time. Let’s consider a few example of different possible thread states and how their thread stack trace looks like.

Running State Example

The example below shows the Thread in Running State. The thread’s run function is continuously looping and not doing anything which means it will be in the Running state most of the time.

class RunnableThread extends Thread {
@Override
public void run() {
while (true) {
}
}
}

The stack trace snippet for this thread – ‘RUNNABLE Example’ is shown below using the printStackTrace method that we wrote in the above section.

Thread Name:RUNNABLE Example
Thread State:RUNNABLE
com.cleantutorials.jconsole.thread.RunnableThread.run(ThreadState.java:67)

The first and second line of the output display the name of the thread and the state of the thread respectively. The next indented line is the actual stack trace which is the stack of functions called till now.

Waiting State Example

Waiting state is usually seen when the thread is waiting to be notified using the wait() function as seen in the example below.

class WaitingThread extends Thread {
@Override
public void run() {
synchronized (this) {
try {
// Thread waits forever.
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

The stack trace of the above thread is given below,

Thread Name:WAITING Example
Thread State:WAITING
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Unknown Source)
com.cleantutorials.jconsole.thread.WaitingThread.run(ThreadState.java:81)

We can see that the stack trace actually contains 3 items now, the item at the end of the stack is the first function call in the Thread i.e the run() call and the last item in the stack is the last call i.e wait().

Timed Waiting State Example

Since the wait here is bounded by time i.e the thread will be eligible to run after the specified sleep period has ended it’s called a timed wait. Timed wait is different from the wait state which waits forever till the thread is notified.

class TimedWaitingThread extends Thread {
@Override
public void run() {
try {
// Timed waiting using sleep.
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

The stack trace for the thread in timed waiting state is given below,

Thread Name:TIMED_WAITING Example
Thread State:TIMED_WAITING
java.lang.Thread.sleep(Native Method)
com.cleantutorials.jconsole.thread.TimedWaitingThread.run(ThreadState.java:97)

The stack trace for above example has 2 items i.e run() function of the thread and the Thread.sleep() called within the run function.

Blocked State Example

A blocked waiting state can occur when a thread is waiting to acquire a lock that is currently held by another thread. Consider the example below where the Lock Thread acquires a lock on the class and BlockedThread tries to acquire the same lock.

class Lock extends Thread {
@Override
public void run() {
synchronized (Lock.class) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Blocked extends Thread {
@Override
public void run() {
synchronized (Lock.class) {
}
}
}

The stack trace generated from our function display the Thread is in BLOCKED state but doesn’t show what it’s blocked on.

Thread Name:BLOCKED Example
Thread State:BLOCKED
com.cleantutorials.jconsole.thread.Blocked.run(ThreadState.java:110)

To analyze the reason why your Thread is blocked you can use tools like JConsole to get real time Thread state without writing your own custom logic to print the stack trace. The deadlock detection tutorial contains examples of Blocked threads and how you can detect the reason why they are blocked using JConsole easily.

CommentsLoad Comments