Java中的程序死锁检测

67

我如何在Java程序中以编程方式检测死锁的发生?


1
你应该明确指定是需要通过编程方式检测还是通过某种外部监控工具? - oxbow_lakes
1
JavaSpecialist网站(值得一读)有一篇有趣的文章(http://www.javaspecialists.eu/archive/Issue130.html),讨论了这个问题的理论和实践。 - Brian Agnew
啊,我的回答假设使用了一个工具。不过无论如何,了解这两种方法都是有用的。 - RichardOD
9个回答

70

您可以使用随JDK一起提供的ThreadMXBean来以编程方式实现此操作:

ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads(); // Returns null if no threads are deadlocked.

if (threadIds != null) {
    ThreadInfo[] infos = bean.getThreadInfo(threadIds);

    for (ThreadInfo info : infos) {
        StackTraceElement[] stack = info.getStackTrace();
        // Log or store stack trace information.
    }
}

显然,你应该尝试隔离执行死锁检查的线程,否则如果该线程发生死锁,它将无法运行检查!

顺便说一下,这就是 JConsole 在背后使用的方法。


4
那个方法的Javadoc说它可能很耗费资源。你知道具体有多耗费资源吗? - Bart van Heukelom
1
不,我没有,尽管我猜你可以编写一些代码来计时操作,并查看随着线程数/锁数量增加,它如何扩展。 - Adamski
这段代码存在几个问题。首先,findDeadlockedThreads 可能会为某些线程返回 null,这可能会导致您的循环出现 NPE 异常。其次,这将不会打印实际的堆栈跟踪,您需要从线程信息中获取实际的线程。您可以查看此帖子以获取更多信息:http://korhner.github.io/java/multithreading/detect-java-deadlocks-programmatically/。 - korhner
我对findDeadlockedThreads方法进行了基准测试(在HotSpot Server VM上,只有很少的线程,诚然)。每次调用在预热后不到100微秒。http://tinybrain.de/1009294 - Stefan Reich
更多线程的基准测试结果:对于100个休眠线程,检测时间小于250微秒。对于1000个休眠线程,它需要小于4毫秒。仍然非常适合偶尔使用。 - Stefan Reich
显示剩余4条评论

14

一个有用的调查提示:

如果你能够在应用程序出现死锁的情况下及时捕捉到它,可以在java.exe控制台窗口上按下"Ctrl-Break"(或者在Solaris/Linux上按下"Ctrl-\"),JVM将会输出所有线程的当前状态和堆栈追踪信息,从而找出死锁并准确描述它们。

输出内容大致如下:

Full thread dump Java HotSpot(TM) Client VM (1.5.0_09-b03 mixed mode):

"[Test Timer] Request Queue" prio=6 tid=0x13d708d0 nid=0x1ec in Object.
    wait() [0x1b00f000..0x1b00fb68]
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Unknown Source)
    at library.util.AsyncQueue.run(AsyncQueue.java:138)
        - locked <0x02e70000> (a test.server.scheduler.SchedulerRequestQueue)

    ...

Found one Java-level deadlock:
=============================
"Corba service":
  waiting to lock monitor 0x13c06684 (object 0x04697d90, a java.lang.Object),
  which is held by "[Server Connection] Heartbeat Timer"
"[Server Connection] Heartbeat Timer":
  waiting to lock monitor 0x13c065c4 (object 0x0467e728, a test.proxy.ServerProxy), which is held by "Corba service"

Java stack information for the threads listed above:
===================================================
"Corba service":
    at test.proxy.ServerProxy.stopHBWatchDog(ServerProxy:695)
    - waiting to lock <0x04697d90> (a java.lang.Object)
    ...

1
或从Solaris/Linux使用Ctrl + \。 - Tom Hawtin - tackline
2
实际上,您想要发送一个“QUIT”信号(即SIGQUIT),但它并不是真正的“QUIT”。在OS X、Linux(和Solaris)上,对Java应用程序发送SIGQUIT会转储堆栈跟踪。Ctrl+\是发送SIGQUIT的一种方式。获取pid并执行:kill -3 {id} 是另一种方法。 - SyntaxT3rr0r

6

您可以使用ThreadMXBean类来编程地检测死锁线程。以下是代码,

    ThreadMXBean bean = ManagementFactory.getThreadMXBean();

    long ids[] = bean.findMonitorDeadlockedThreads();

    if(ids != null)
    {
        ThreadInfo threadInfo[] = bean.getThreadInfo(ids);

        for (ThreadInfo threadInfo1 : threadInfo)
        {
            System.out.println(threadInfo1.getThreadId());    //Prints the ID of deadlocked thread

            System.out.println(threadInfo1.getThreadName());  //Prints the name of deadlocked thread

            System.out.println(threadInfo1.getLockName());    //Prints the string representation of an object for which thread has entered into deadlock.

            System.out.println(threadInfo1.getLockOwnerId());  //Prints the ID of thread which currently owns the object lock

            System.out.println(threadInfo1.getLockOwnerName());  //Prints name of the thread which currently owns the object lock.
        }
    }
    else
    {
        System.out.println("No Deadlocked Threads");
    }

点击这里了解更多有关如何检测死锁线程的信息。


最好使用findDeadlockedThreads()而不是findMonitorDeadlockedThreads()。findDeadlockedThreads在1.6中被引入,与findMonitorDeadlockedThreads()相比更好,因为后者可能会丢失一些死锁情况并且无法报告它们。它无法报告ReentrankLock或ReentrantReadWriteLock的死锁情况。 更多信息可以在此处找到:https://meteatamel.wordpress.com/2012/03/21/deadlock-detection-in-java/,标题为“findMonitorDeadlockedThreads vs. findDeadlockedThreads”。 - Investigator

3

JArmus 是用于死锁检测和避免的库。它支持以下内容:Thread.joinCyclicBarrierCountDownLatchPhaserReentrantLock

要使用 JArmus,您需要对代码进行仪器化。可以通过其经过仪器化的类之一或使用 JArmus 仪器化程序jarmusc 自动进行。

java -jar jarmusc.jar yourprogram.jar checkedprogram.jar

输入yourprogram.jar是您要检查的程序。 输出是具有检查以自动查找任何死锁的相同程序。

屏障需要一些帮助

使用类CyclicBarrierCountDownLatchPhaser验证死锁有点棘手---例如,JConsole无法检测到这些类型的死锁。JArmus 需要您的一点帮助:您必须指定哪些线程影响同步,我们将这些称为已注册线程。

尽快,线程必须标记自己已注册。标记已注册线程的好地方是在Runnable.run方法的开始处。 JArmus.register(latch);

示例

下面使程序发生死锁,可以被 JArmus 正确识别:

final CountDownLatch latch = new CountDownLatch(2);
final CyclicBarrier barrier = new CyclicBarrier(2);
final Queue<Exception> exceptions = new ArrayDeque<>();
Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            JArmus.register(barrier); // do not forget to register!
            JArmus.register(latch); // do not forget to register!
            latch.countDown();
            latch.await();
            barrier.await();
        } catch (Exception e) {
            exceptions.add(e);
        }
    }
});
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            JArmus.register(barrier); // do not forget to register!
            JArmus.register(latch); // do not forget to register!
            barrier.await();
            latch.countDown();
            latch.await();
        } catch (Exception e) {
            exceptions.add(e);
        }
    }
});
t1.start();
t2.start();

2

3
"MTRAT"链接指向一个关于浮点算术的页面。不理解它是关于什么的。 - Stefan Reich

2

如果您不需要编程检测,可以通过 JConsole 完成;在线程选项卡中有一个“检测死锁”按钮。在JDK6中,此功能可检测内部监视器和 j.u.c Lock 的锁。

通过使用$JAVA_HOME/bin/jconsole 命令启动 JConsole。


1

这里有一些代码:http://www.java2s.com/Code/Java/Development-Class/PerformingdeadlockdetectionprogrammaticallywithintheapplicationusingthejavalangmanagementAPI.htm

魔法发生在ThreadMonitor.findDeadlock()中:

  public boolean findDeadlock() {
    long[] tids;
    if (findDeadlocksMethodName.equals("findDeadlockedThreads")
        && tmbean.isSynchronizerUsageSupported()) {
      tids = tmbean.findDeadlockedThreads();
      if (tids == null) {
        return false;
      }

      System.out.println("Deadlock found :-");
      ThreadInfo[] infos = tmbean.getThreadInfo(tids, true, true);
      for (ThreadInfo ti : infos) {
        printThreadInfo(ti);
        printLockInfo(ti.getLockedSynchronizers());
        System.out.println();
      }
    } else {
      tids = tmbean.findMonitorDeadlockedThreads();
      if (tids == null) {
        return false;
      }
      ThreadInfo[] infos = tmbean.getThreadInfo(tids, Integer.MAX_VALUE);
      for (ThreadInfo ti : infos) {
        // print thread information
        printThreadInfo(ti);
      }
    }

    return true;
  }

这个调用ThreadMXBean的API在Java 5和6中有不同的名称(因此有外部的if())。

代码示例还允许中断锁定,因此您甚至可以打破死锁。


1

tempus-fugit 还实现了一个编程式线程转储类。它使用上述的 MBean 机制实现,并提供了一个即插即用的超级解决方案。


0

如果您希望在运行时完成此操作,可以使用看门狗


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接