Analytics

Thursday, July 4, 2013

Static Initialization Deadlock

Time some more fun with static initialization in the JVM. I previously wrote about how static initializers are not hotspot-optimized, which can lead to some interesting performance issues, but this time we see something more scary in seemingly innocent code. Take a look:


Here we have a class MyClass (it is an inner class here for simplicity only) which has a static inner class MySubClass. MyClass also has a static field of type MySubClass; that field will be initialized when MyClass is. Then in the main method we have a thread that creates an instance of MySubClass, and we directly reference the static field on MyClass. We aren't really doing a whole lot here; in particular, there is no explicit synchronization, which is usually good enough to rule out things like race conditions and deadlocks. Well, if you run this a few times, you will in fact notice that this program sometimes fails to terminate. Putting jstack to work, we see the following stack traces:


Well, that's interesting; the thread we created is just stuck on the line trying to create the instance of MySubClass while the main thread is trying to initialize MyClass. As it turns out, static initialization in the JVM is thread-safe which is good, but the synchronization introduced used can cause a deadlock. If you recall the necessary conditions for deadlock, one of them is circular wait; that is, threads acquiring locks in a potentially cyclic order. In this case, the two classes MyClass and MySubClass have individual locks guarding their loading and initialization, but initializing one in turn triggers the initialization of the other (hence the possibility of circular wait). Initializing MySubClass naturally has to initialize the superclass, while the presence of the static field in MyClass causes the reverse dependency, so both of our threads get stuck during the class initialization process.

This is quite unfortunate because deadlocks are extremely hard to reproduce and track down, especially when you do not suspect your code of having such problems. And while my example may seem a bit contrived (although less so than they typically are), it is based on a real bug we encountered in a production environment. If a bug exists in the JVM, there is enough Java code out there that someone will almost certainly run into it.

1 comment:

  1. Hi Jeffrey,

    I think there is a solution to this problem in a specific case, which is when the MySubclass instance may be a singleton, as in:

    public class StaticInitDeadlock {

    public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(MyClass::getInstance);
    thread.start();

    MyClass subClass = MyClass.getInstance();
    thread.join();
    }

    public static class MyClass {

    private MyClass() {}

    private static final MyClass.MySubClass SUB_CLASS = new MyClass.MySubClass();

    public static class MySubClass extends MyClass {
    private MySubClass() {}
    }

    public static MyClass getInstance() {
    return SUB_CLASS;
    }
    }
    }

    In such a case, some code checking tools still report the problem although I don't think it can still happen. However, I could not verify this since I was never able to observe the bug in either cases!

    ReplyDelete