12

(Whether using static initializers in Java is a good idea is out of scope for this question.)

I am encountering deadlocks in my Scala application, which I think are caused by interlocking static initializers in the compiled classes.

My question is how to detect and diagnose these deadlocks -- I have found that the normal JVM tools for deadlocks do not seem to work when static initializer blocks are involved.

Here is a simple example Java app which deadlocks in a static initializer:

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();
    }
}

If you launch this app, it deadlocks. The stack trace at time of deadlock (from jstack) contains the following two deadlocked threads:

"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

My questions are as follows

  1. Why is the first thread marked as RUNNABLE, when it is in fact waiting on a lock? Can I detect the "real" state of this thread somehow?
  2. Why is neither thread marked as owning any (relevant) locks, when in fact one holds the static intializer lock and the other is waiting for it? Can I detect the static initializer lock ownership somehow?
Rich
  • 15,048
  • 2
  • 66
  • 119
  • Why do you think it is waiting on a lock? If it was waiting on a lock it would (1) be in state WAITING, not RUNNABLE and (2), would mention "-waiting on ..." or "- waiting to lock" after the top entry of the stack. – Dima Dec 18 '14 at 15:17
  • 3
    I don't think [these initialization locks](http://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.2) are visible. They are managed by the JVM for this process specifically. Maybe the thread dumps don't reveal them. – Sotirios Delimanolis Dec 18 '14 at 15:17
  • @Dima -- I know that it is waiting on the lock, because I constructed the code so that it would. The above program will deadlock forever if you run it -- try it yourself. Is your point that it's not using a `java.util.concurrent.locks`? If so -- that's right, but not very helpful. – Rich Dec 18 '14 at 15:26
  • @SotiriosDelimanolis yes, the locks do not appear to be visible in the thread dumps. There is a worked example in the question demonstrating this fact. My question is how to make them visible. – Rich Dec 18 '14 at 15:27
  • Its a very interesting question, but IMO the example is a little... poor. Initializers are for initializing static state, yet the initializer here is not only abused to run business logic, it is abused to start a separate thread. Poor, poor JVM. – Gimby Dec 18 '14 at 15:28
  • @Gimby - I agree. My real motivating example is in Scala and involves lambdas closing over member variables in Objects (Scala's equivalent of static classes, which are implemented via Java static intializers). Can you think of a "nice" static initializer deadlock in Java? – Rich Dec 18 '14 at 15:49

2 Answers2

1

Scala makes it easy to fall into the trap.

The easy workaround or diagnostic (if you see clinit in your stack trace) is to let your object extend App to let DelayedInit take your code off the static initializer.

Some clarifying links:

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

Community
  • 1
  • 1
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • Thanks. Do you know if it is possible to detect these deadlocks? The threads are listed as RUNNABLE, and the implicit static lock is not listed in the locks owned by the threads. – Rich Feb 12 '15 at 09:56
1

I have tried this example with my tool and it also fails in detecting this as a deadlock. After struggling a bit with the jconsole debugger and re-running the example a couple of times I noticed that the initial thread is marked as RUNNABLE because it is runnable, the problem here is that since the launched thread access to a static member, this operation is queued after the finishing of the static initializer block (this semantic is not clear in the JVM specification, however it seems to be the case).

The static initializer does not finish because in this bizarre example a join operation forces it to wait for the thread termination, however I remark that this "queued" operation does not grab a lock explicitly nor implicitly according to the JVM specification. Perhaps this shouldn't be considered a deadlock per se as it would be the same case if the body of the run method contains an infinite loop.

halfer
  • 19,824
  • 17
  • 99
  • 186
Abel Garcia
  • 178
  • 1
  • 12
  • 1
    Well, it's not documented as a lock, but it acts exactly like a lock. I think it's a "secret" inner lock that the JVM doesn't let userland interact with or inspect. I think that it would be cheating to define the problem away by saying that as it's not documented as a lock, it can't be a deadlock. – Rich Nov 14 '16 at 14:04
  • 1
    "the initial thread is marked as RUNNABLE because it is runnable" -- it is not runnable in the normal meaning of the word, it is blocked waiting for the the hidden static initializer lock. My question is 1) how can I detect that a thread is in that state and 2) how can I detect which static initializers it is waiting for? I strongly suspect that the answers to both are "you cannot, as the JVM does not expose this info" – Rich Nov 14 '16 at 14:10