如何写一个jstack检测不到的死锁

通常java程序排查死锁的办法是通过jstack 打印出线程信息,里面会直接显示发生死锁的情况。这里我们先解释下查死锁可以采用的两种办法。然后我们写一个用普通的方法检测不到的死锁。

这里我们先贴一个简单的发生死锁的代码。

//class A
public classAimplementsRunnable {
     public void run() {
        synchronized (B.class){
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (A.class){
                System.out.println("A println: i am finished");
            }
        }
    }
}
//class B
public classBimplementsRunnable {
    public void run() {
        synchronized (A.class){
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B.class){
                System.out.println("B println: i am finished");
            }
        }
    }
}
//main class
public classABTest {
    public static void main(String[]args){
        ExecutorService executorService= Executors.newCachedThreadPool();
        executorService.submit(new A());
        executorService.submit(new B());
    }
}

jstack查死锁办法

jstack查看死锁只需要两步:

  • jps 或者 ps 拿到对应的java进程号<pid>。
  • jstack <pid>

这样我们就能拿到对应进程的线程信息,比如运行上面发生死锁的代码,然后执行jstack我们就可以看到如下信息(省落部分信息)。

"pool-1-thread-2":
    at com.yao.bytecode.B.run(B.java:22)
    - waiting to lock <0x00000007956f8a10> (a java.lang.Class for com.yao.bytecode.B)
    - locked <0x00000007956f4cb8> (a java.lang.Class for com.yao.bytecode.A)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
"pool-1-thread-1":
    at com.yao.bytecode.A.run(A.java:19)
    - waiting to lock <0x00000007956f4cb8> (a java.lang.Class for com.yao.bytecode.A)
    - locked <0x00000007956f8a10> (a java.lang.Class for com.yao.bytecode.B)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Found one Java-level deadlock:
"pool-1-thread-2":
  waiting to lock monitor 0x00007fb42c020cc8 (object 0x00000007956f8a10, a java.lang.Class),
  which is held by "pool-1-thread-1"
"pool-1-thread-1":
  waiting to lock monitor 0x00007fb42c0234a8 (object 0x00000007956f4cb8, a java.lang.Class),
  which is held by "pool-1-thread-2"

上面的信息很清楚显示pool-1-thread-1和 pool-1-thread-2在相互等待对方锁住的锁。

利用ThreadMXBean 查死锁

这种办法是通过JMX(Java管理扩展)提供的管理接口ThreadMXBean来查看有没有死锁发生。 不了解的JMX的基本使用的同学可以通过下面的例子了解或者直接去搜索了解下。放个的小例子,也是从网上搜到的。

//EchoMBean
public interfaceEchoMBean{
    public String print(String name);
}
//Echo
public classEchoimplementsEchoMBean{

    public String print(String name) {
        System.out.println("hi "+ name);
        return "hi "+name;
    }
}
//
public classMbeanTest{
    public static void main(String[]args) throws Exception {
        MBeanServer mBeanServer= ManagementFactory.getPlatformMBeanServer();
       //这里包名要和实现类Echo的包名一致
        ObjectName name=new ObjectName("com.yao.mbean:type=Echo");
        Echo mbean=new Echo();
        mBeanServer.registerMBean(mbean,name);
        mBeanServer.invoke(name,"print",new Object[]{"hello"},new String[]{"java.lang.String"});
        TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
    }
}

运行上面的代码,然后打开jconsole 或者jprofiler(需要自己安装)界面,连接到我们的上面开的程序,然后就可以通过界面操作我们注入的管理bean,输入方法参数点击执行,我们就可以在后台看到我们想要的执行。
输入图片说明
简单介绍下Mbean的使用后我继续说下ThreadMXBean,这个接口我们主要看findMonitorDeadlockedThreads方法和查看具体线程信息方法getThreadInfo。因为jvm启动时,会自动把ThreadMXBean的实现类注入到管理平台中,因此我们可以直接通过jprofiler -> MBeans 找到java.lang Threading,然后点击operation,执行findMonitorDeadlockedThreads。即可看到结果。
输入图片说明
然后就可以拿到发生死锁的线程id,在通过id 用getThreadInfo 看具体的信息。

jstack检测不到的死锁

介绍这么多,我们还没说怎么写个jstack检测不到的死锁。下面直接看代码,里面逻辑摘自《实战Java虚拟机》。直接先上代码:

public classStaticA{
    static {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            Class.forName("com.yao.bytecode.StaticB");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("StaticA init OK");
    }
}
public classStaticB{
    static {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            Class.forName("com.yao.bytecode.StaticA");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("StaticB init OK");
    }
}

public classStaticABTestextendsThread{
    private String flag;
    publicStaticABTest(String flag){
        this.flag = flag;
    }
    @Override
    publicvoidrun(){
        try {
            Class.forName("com.yao.bytecode.Static"+flag);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    publicstaticvoidmain(String[]args)throws Exception {
        StaticABTest staticA=new StaticABTest("A");
        StaticABTest staticB=new StaticABTest("B");
        staticA.start();
        staticB.start();
        staticA.join();
        staticB.join();
    }
}

运行上面代码,会发生死锁,程序一直运行,然后运行我们的jstack去获取thread信息,我们看不到任何死锁信息,用ThreadMXBean 也看不到(本质和jstack查死锁的原理差不多)。这是为什呢?

这和JVM初始化java bean的逻辑有关系,我们都知道JVM加载一个class 会有很多步骤:加载-> 连接(验证,准备,解析)->初始化。在初始化步骤中,JVM会执行类编译后的cinit函数,而static块里的逻辑会被编译器放到cinit函数中,当JVM执行cinit时会给cinit加上锁,防止多线程并发执行。因此当staticA staticB 进行初始化时都加上自己初始化的锁,然后在通过Class.forName去加载对方,因此都想获取对方要执行cinit的锁,因此死锁就此发生。因此大家在写代码时一定要避免上面的写法,否则用常规的方法根本监测定位不到死锁。

https://my.oschina.net/robinyao/blog/807450

 

猜你喜欢

转载自m635674608.iteye.com/blog/2346407