【问题标题】:How to diagnose or detect deadlocks in Java static initializers如何诊断或检测 Java 静态初始化程序中的死锁
【发布时间】:2014-12-18 15:09:49
【问题描述】:

(在 Java 中使用静态初始化器是否是一个好主意超出了这个问题的范围。)

我在我的 Scala 应用程序中遇到死锁,我认为这是由已编译类中的互锁静态初始化程序引起的。

我的问题是如何检测和诊断这些死锁——我发现当涉及到静态初始化块时,用于死锁的普通 JVM 工具似乎不起作用。

这是一个简单的 Java 应用示例,它在静态初始化程序中死锁:

public class StaticDeadlockExample implements Runnable
{
    static
    {
        Thread thread = new Thread(
                new StaticDeadlockExample(),
                "StaticDeadlockExample child thread");
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        System.out.println("in main");
    }

    public static void sayHello()
    {
        System.out.println("hello from thread " + Thread.currentThread().getName());
    }

    @Override
    public void run() {
        StaticDeadlockExample.sayHello();
    }
}

如果您启动此应用程序,它会死锁。死锁时的堆栈跟踪(来自jstack)包含以下两个死锁线程:

"StaticDeadlockExample child thread" prio=6 tid=0x000000006c86a000 nid=0x4f54 in Object.wait() [0x000000006d38f000]
   java.lang.Thread.State: RUNNABLE
    at StaticDeadlockExample.run(StaticDeadlockExample.java:37)
    at java.lang.Thread.run(Thread.java:619)

   Locked ownable synchronizers:
    - None

"main" prio=6 tid=0x00000000005db000 nid=0x2fbc in Object.wait() [0x000000000254e000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000004a6a7870> (a java.lang.Thread)
    at java.lang.Thread.join(Thread.java:1143)
    - locked <0x000000004a6a7870> (a java.lang.Thread)
    at java.lang.Thread.join(Thread.java:1196)
    at StaticDeadlockExample.<clinit>(StaticDeadlockExample.java:17)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:169)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:116)

   Locked ownable synchronizers:
    - None

我的问题如下

  1. 为什么第一个线程被标记为 RUNNABLE,而实际上它正在等待锁?我能以某种方式检测到这个线程的“真实”状态吗?
  2. 为什么两个线程都没有标记为拥有任何(相关)锁,而实际上一个线程持有静态初始化程序锁而另一个正在等待它?我能以某种方式检测静态初始化锁的所有权吗?

【问题讨论】:

  • 为什么你认为它正在等待锁?如果它正在等待一个锁,它将 (1) 处于 WAITING 状态,而不是 RUNNABLE 并且 (2) 在堆栈的顶部条目之后会提到“-waiting on ...”或“- waiting to lock”。
  • 我认为these initialization locks 不可见。它们由 JVM 专门为此进程管理。也许线程转储没有显示它们。
  • @Dima -- 我知道它正在等待 锁,因为我构建了代码以便它可以。如果你运行上面的程序,它会永远死锁——你自己试试。你的意思是它没有使用java.util.concurrent.locks吗?如果是这样 - 那是对的,但不是很有帮助。
  • @SotiriosDelimanolis 是的,锁似乎在线程转储中不可见。问题中有一个工作示例证明了这一事实。我的问题是如何让它们可见。
  • 这是一个非常有趣的问题,但 IMO 这个例子有点……差。初始化器用于初始化静态状态,但这里的初始化器不仅被滥用于运行业务逻辑,还被滥用于启动一个单独的线程。可怜的,可怜的 JVM。

标签: java scala deadlock


【解决方案1】:

Scala 让人很容易掉入陷阱。

简单的解决方法或诊断(如果您在堆栈跟踪中看到 clinit)是让您的对象扩展 App 以让 DelayedInit 将您的代码从静态初始化程序中取出。

一些澄清链接:

https://issues.scala-lang.org/browse/SI-7646

Scala: Parallel collection in object initializer causes a program to hang

http://permalink.gmane.org/gmane.comp.lang.scala.user/72499

【讨论】:

  • 谢谢。你知道是否有可能检测到这些死锁吗?线程被列为 RUNNABLE,隐式静态锁未列在线程拥有的锁中。
【解决方案2】:

我已经用my tool 尝试了这个例子,但它也未能将其检测为死锁。在使用 jconsole 调试器并重新运行了几次示例之后,我注意到初始线程被标记为 RUNNABLE 因为它是可运行的,这里的问题是,由于启动的线程访问静态成员,所以这个操作在静态初始化程序块完成后排队(此语义在 JVM specification 中不清楚,但似乎是这样)。

静态初始化程序没有完成,因为在这个奇怪的示例中,连接操作强制它等待线程终止,但是我注意到这个“排队”操作不会根据 JVM 规范显式或隐式获取锁。也许这不应该被视为死锁本身,因为如果 run 方法的主体包含无限循环,情况也是如此。

【讨论】:

  • 嗯,它没有被记录为锁,但它的作用与锁完全一样。我认为这是 JVM 不允许用户空间与之交互或检查的“秘密”内部锁。我认为通过说由于它没有被记录为锁,它不能是死锁来定义问题是欺骗性的。
  • “初始线程被标记为 RUNNABLE 因为它是可运行的”——它在正常的词义上是不可运行的,它被阻塞等待隐藏的静态初始化器锁。我的问题是 1)如何检测线程处于该状态,以及 2)如何检测它正在等待哪些静态初始化程序?我强烈怀疑两者的答案都是“你不能,因为 JVM 不会公开这些信息”
猜你喜欢
  • 2010-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多