69 How to Locate Deadlocks With Command Line and Code

69 How to Locate Deadlocks With Command Line and Code #

In this lesson, we mainly introduce “how to locate deadlocks with command line and code”.

Before that, we have introduced what deadlock is and the necessary conditions for deadlock to occur. Even if we write the code very carefully, it is still possible that deadlocks may occur. Once a deadlock occurs, the first step is to find it, because after finding and locating the deadlock, we can take follow-up remedial measures, such as resolving the deadlock, recovering after resolving the deadlock, optimizing the code, etc. If the deadlock cannot be found, the subsequent steps cannot be discussed.

Now let’s see how to use the command line to find deadlocks.

Command: jstack #

This command is called jstack, which can see some related information of our Java threads. If it is an obvious deadlock relationship, this tool can detect it directly. If the deadlock is not obvious, it cannot be detected directly. However, we can analyze the thread status to discover the mutual dependency of locks, so this is also very helpful for us to find deadlocks.

Let’s try it out by executing this command.

First, let’s run the MustDeadLock class that is guaranteed to deadlock from lesson 67:

/**
 * Description: A situation where deadlock must occur
 */
public class MustDeadLock implements Runnable {
    public int flag;

    static Object o1 = new Object();
    static Object o2 = new Object();

    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName() + " flag is " + flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("Thread 1 gets both locks");
                }
            }
        }
        if (flag == 2) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                }  catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("Thread 2 gets both locks");
                }
            }
        }
    }

    public static void main(String[] argv) {
        MustDeadLock r1 = new MustDeadLock();
        MustDeadLock r2 = new MustDeadLock();
        r1.flag = 1;
        r2.flag = 2;
        Thread t1 = new Thread(r1, "t1");
        Thread t2 = new Thread(r2, "t2");
        t1.start();
        t2.start();
    }
}

Since it encounters a deadlock, the program will not stop running without intervention. Then open our terminal and execute the command ${JAVA_HOME}/bin/jps to view the pid of the current Java program. The results of my execution are as follows:

56402 MustDeadLock
56403 Launcher
56474 Jps
55051 KotlinCompileDaemon

Here, we can see that the first line is the pid (56402) of MustDeadLock class. Then we continue to execute the next command, ${JAVA_HOME}/bin/jstack followed by a space and the pid we just obtained, 56402. So the complete command is ${JAVA_HOME}/bin/jstack 56402. Finally, it will print out a lot of information, including the information about thread lock acquisition, such as which thread acquires which lock, where it acquires the lock, and what locks it is waiting for or holding. These important information will be printed out. We extract a part of the useful information related to deadlocks, as shown below:

Found one Java-level deadlock:
=============================
"t2":
  waiting to lock monitor 0x00007fa06c004a18 (object 0x000000076adabaf0, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x00007fa06c007358 (object 0x000000076adabb00, a java.lang.Object),
  which is held by "t2"
Java stack information for the threads listed above:
===================================================
"t2":
	at lesson67.MustDeadLock.run(MustDeadLock.java:31)
	- waiting to lock <0x000000076adabaf0> (a java.lang.Object)
	- locked <0x000000076adabb00> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)

“t1”:

at lesson67.MustDeadLock.run(MustDeadLock.java:19)

- waiting to lock <0x000000076adabb00> (a java.lang.Object)

- locked <0x000000076adabaf0> (a java.lang.Object)

at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock

Here, it first prints “Found one Java-level deadlock”, indicating that a deadlock has been found. Then there is more detailed information. From this part of the information, we can see that the t2 thread is trying to acquire the lock object with the suffix af0, but it is held by the t1 thread. At the same time, t2 is holding the lock object with the suffix b00. Similarly, t1 is trying to acquire the lock object with the suffix b00, but it is held by the t2 thread, and t1 is holding the lock object with the suffix af0. This forms a circular dependency and a deadlock occurs. Finally, it also prints “Found 1 deadlock.”, indicating that the jstack tool not only helps us find the deadlock, but also tells us which thread, which lock it is trying to acquire, and what kind of circular dependency it forms. With this information, it is easy to locate the deadlock, so we can further modify the code to avoid the deadlock.

The above is the method of using jstack to locate deadlocks. jstack can help us analyze the locks held by threads and the locks they need, and then analyze whether there is a circular dependency forming a deadlock.

Code: ThreadMXBean #

Now let’s take a look at the method of using code to locate deadlocks.

We will use the ThreadMXBean utility class. The code example is as follows:

public class DetectDeadLock implements Runnable {

    public int flag;

    static Object o1 = new Object();

    static Object o2 = new Object();

    public void run() {

        System.out.println(Thread.currentThread().getName()+" flag = " + flag);

        if (flag == 1) {

            synchronized (o1) {

                try {

                    Thread.sleep(500);

                } catch (Exception e) {

                    e.printStackTrace();

                }

                synchronized (o2) {

                    System.out.println("Thread 1 acquired both locks");

                }

            }

        }

        if (flag == 2) {

            synchronized (o2) {

                try {

                    Thread.sleep(500);

                } catch (Exception e) {

                    e.printStackTrace();

                }

                synchronized (o1) {

                    System.out.println("Thread 2 acquired both locks");

                }

            }

        }

    }

    public static void main(String[] argv) throws InterruptedException {

        DetectDeadLock r1 = new DetectDeadLock();

        DetectDeadLock r2 = new DetectDeadLock();

        r1.flag = 1;

        r2.flag = 2;

        Thread t1 = new Thread(r1,"t1");

        Thread t2 = new Thread(r2,"t2");

        t1.start();

        t2.start();

        Thread.sleep(1000);

        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

        long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();

        if (deadlockedThreads != null && deadlockedThreads.length > 0) {

            for (int i = 0; i < deadlockedThreads.length; i++) {

                ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);

                System.out.println("Thread with id "+threadInfo.getThreadId()+", name " + threadInfo.getThreadName()+" is deadlocked and needs the lock currently held by thread "+threadInfo.getLockOwnerName()+".");

            }

        }

    }

}

This class is an upgraded version of the previous MustDeadLock class. The main purpose of the MustDeadLock class is to allow thread 1 and thread 2 to acquire the locks o1 and o2 in different orders, and form a deadlock. In the main function, after starting t1 and t2, the code that follows is the code we added this time. We use Thread.sleep(1000) to ensure that the deadlock has already occurred, and then we use ThreadMXBean to check for deadlocks.

Using the findDeadlockedThreads method of ThreadMXBean, we can get an array of deadlockedThreads, and then perform a check. When this array is not empty and its length is greater than 0, we print out the corresponding thread information one by one. For example, we print out the thread id, thread name, and the thread that is holding the lock it needs. The execution result of this part of the code is as follows:

t1 flag = 1

t2 flag = 2

Thread with id 12, name t2 is deadlocked and needs the lock currently held by thread t1.

Thread with id 11, name t1 is deadlocked and needs the lock currently held by thread t2.

There are four lines of statements in total. The first two lines are “t1 flag = 1” and “t2 flag = 2”, which are the content printed before the deadlock occurs. The next two lines are the results of the deadlock detection. It can be seen that it prints “Thread with id 12, name t2 is deadlocked and needs the lock currently held by thread t1.” Similarly, it also prints “Thread with id 11, name t1 is deadlocked and needs the lock currently held by thread t2.”

It can be seen that ThreadMXBean can also help us find and locate deadlocks. If we add such detection in the business code, we can locate the deadlock in a timely manner when it occurs, and perform other actions such as alerting, thereby enhancing the robustness of our program.

Summary #

Let’s summarize. In this lesson, we introduced two ways to locate deadlocks in code. When a deadlock occurs, we can use the jstack command or use the ThreadMXBean in the code to help us find the deadlock.