Java程序栈信息文件中的秘密(二)

来源:互联网 发布:移除数组中的指定元素 编辑:程序博客网 时间:2024/06/05 12:03

栈信息本身并不神秘,格式和内容都比较单一,但借助程序的调用栈信息可以观察到很多有用的信息。借助栈信息来分析有时候被认为是一种秘技,实际上这是一种误解。古人云,读书百遍,其义自见,程序调用栈信息看多了自然就会明白。下面结合java.lang.Thread.State类的注释,举几个代码及其调用栈的例子,相信对于聪明的同学,入门就不困难了。但原理什么的,就得靠自己了,至少得读一下java.lang.Thread.State类的注释吧。

下面的例子都是使用Oracle公司出品的Java7编译和测试的,具体的版本信息如下。

java -versionjava version "1.7.0_11"Java(TM) SE Runtime Environment (build 1.7.0_11-b21)Java HotSpot(TM) Client VM (build 23.6-b04, mixed mode, sharing)

例子一

public class DeadLock {    public static void main(final String[] args) throws Exception {        final Object obj = new Object();        new Thread() {            @Override            public void run() {                callWait(obj);            };        }.start();        Thread.sleep(3000L);        synchronized (obj) {            try {                obj.wait();            }            catch (final InterruptedException e) {                e.printStackTrace();            }        }    }    static void callWait(final Object obj) {        synchronized (obj) {            try {                obj.wait();            }            catch (final InterruptedException e) {                e.printStackTrace();            }        }    }}
这段代码并不复杂,做的事情比较明确,一共存在有两个线程,都会调用callWait方法,在callWait方法中又会对一个对象加锁,调用对应的wait方法。这个例子模拟的错误就是wait方法和notify/notifyAll方法没有成对的调用,导致程序出现死锁现象。问题说到这里,解决的方法自然非常明确,但是重点在根据程序的调用栈来发现问题,因而需要实地执行程序,同时使用jstack工具获取栈信息,了解从栈信息中如何发现问题的根本原因。

"main" prio=6 tid=0x00868800 nid=0x1370 in Object.wait() [0x0098f000]   java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x22bc2c08> (a java.lang.Object)at java.lang.Object.wait(Object.java:503)at DeadLock.main(DeadLock.java:30)- locked <0x22bc2c08> (a java.lang.Object)   Locked ownable synchronizers:- None"Thread-0" prio=6 tid=0x02b22000 nid=0x1044 in Object.wait() [0x0316f000]   java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x22bc2c08> (a java.lang.Object)at java.lang.Object.wait(Object.java:503)at DeadLock$1.run(DeadLock.java:14)- locked <0x22bc2c08> (a java.lang.Object)   Locked ownable synchronizers:- None
上面只是栈文件中的一部分,两个线程的栈信息块之间可能也没有离这么近,为了缩短篇幅,做了一定的裁剪,去掉了问题不相关的部分。可以发现0x22bc2c08一共出现了四次,两段栈信息中各出现再次,顺序一样,都是调用了wait方法后进入了WAITING状态,同时都没有持有对象的锁。有趣的现象哈。

例子二

public class DeadLock {    public static void main(final String[] args) throws Exception {        Thread.currentThread().setName("main thread");        final Thread r = new Thread() {            @Override            public void run() {                try {                    Thread.sleep(200000L);                }                catch (final InterruptedException e) {                    e.printStackTrace();                }            };        };        r.setName("another thread");        r.start();        System.out.println("start to sleeping");        Thread.sleep(100000L);        System.out.println("start to joining");        r.join();    }}
这段样例代码很简单,就是两个线程各自调用了sleep方法,但时间不相同,然后在主函数结尾处,还等待新线程结束。对于这段样例,存在两次抓取栈信息的机会,第一次是屏幕输出“start to sleeping”时,可以截取出如下的栈信息。

"another thread" prio=6 tid=0x02b22400 nid=0x2058 waiting on condition [0x0316f000]   java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at DeadLock$1.run(DeadLock.java:9)   Locked ownable synchronizers:- None"main thread" prio=6 tid=0x00868800 nid=0x2720 waiting on condition [0x0098f000]   java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at DeadLock.main(DeadLock.java:19)   Locked ownable synchronizers:- None
很明显,两个线程当前都处于sleep状态。第二次抓取栈信息是屏幕输出“start to joining”,这时主线程的等待已执行完成,主线程开始等待新线程执行完成,可以截取到如下的栈信息。

"another thread" prio=6 tid=0x02b22400 nid=0x2058 waiting on condition [0x0316f000]   java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at DeadLock$1.run(DeadLock.java:9)   Locked ownable synchronizers:- None"main thread" prio=6 tid=0x00868800 nid=0x2720 in Object.wait() [0x0098f000]   java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x22bc3d70> (a DeadLock$1)at java.lang.Thread.join(Thread.java:1258)- locked <0x22bc3d70> (a DeadLock$1)at java.lang.Thread.join(Thread.java:1332)at DeadLock.main(DeadLock.java:21)   Locked ownable synchronizers:- None
和预期一致,新线程处于sleep状态,而主线程已进入了wait状态。

例子三

public class DeadLock {    public static void main(final String[] args) throws Exception {        final Object lock1 = new Object();        final Object lock2 = new Object();        Thread.currentThread().setName("main-thread");        final Thread r = new Thread("another-thread") {            @Override            public void run() {                try {                    synchronized (lock2) {                        Thread.sleep(20000);                        synchronized (lock1) {                            Thread.sleep(20000);                        }                    }                }                catch (final Exception e) {                    e.printStackTrace();                }            }        };        r.start();        Thread.sleep(5000);        synchronized (lock1) {            Thread.sleep(20000);            synchronized (lock2) {                Thread.sleep(20000);            }        }    }}
这段代码可能有点复杂,但原理不麻烦,就是描述了经典的加锁顺序问题。栈信息比较有趣,裁剪之后如下。

"another-thread" prio=6 tid=0x02b22400 nid=0x2250 waiting for monitor entry [0x0316f000]   java.lang.Thread.State: BLOCKED (on object monitor)at DeadLock$1.run(DeadLock.java:14)- waiting to lock <0x22bc3f90> (a java.lang.Object)- locked <0x22bc3f98> (a java.lang.Object)   Locked ownable synchronizers:- None"main-thread" prio=6 tid=0x00868800 nid=0x2634 waiting for monitor entry [0x0098f000]   java.lang.Thread.State: BLOCKED (on object monitor)at DeadLock.main(DeadLock.java:28)- waiting to lock <0x22bc3f98> (a java.lang.Object)- locked <0x22bc3f90> (a java.lang.Object)   Locked ownable synchronizers:- NoneFound one Java-level deadlock:============================="another-thread":  waiting to lock monitor 0x02ad433c (object 0x22bc3f90, a java.lang.Object),  which is held by "main-thread""main-thread":  waiting to lock monitor 0x02ad363c (object 0x22bc3f98, a java.lang.Object),  which is held by "another-thread"Java stack information for the threads listed above:==================================================="another-thread":at DeadLock$1.run(DeadLock.java:14)- waiting to lock <0x22bc3f90> (a java.lang.Object)- locked <0x22bc3f98> (a java.lang.Object)"main-thread":at DeadLock.main(DeadLock.java:28)- waiting to lock <0x22bc3f98> (a java.lang.Object)- locked <0x22bc3f90> (a java.lang.Object)Found 1 deadlock.

主线程中提示了当前持有的锁和期望获取的锁,而新线程也存在类似的提示,只是获取锁的顺序不同。从栈信息可以看出,两个线程都处于BLOCKED状态。可能是源代码中的问题过于明显,导出的栈信息中给出了死锁的提示信息,实际项目的业务代码比样例要复杂,JVM可能做不到这么智能。这里的提示信息多多少少有点让我失望,在主线程栈信息的最后一部分没有输出当前持有锁的列表,很奇怪,不知道是不是和JDK的实现有关。

样例四

import java.security.SecureRandom;import java.util.concurrent.SynchronousQueue;import java.util.concurrent.ThreadFactory;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.locks.ReentrantLock;public class DeadLock {    private final ReentrantLock lock = new ReentrantLock();    private static final SecureRandom random = new SecureRandom();    public DeadLock() {    }    public void runWork() {        final ThreadPoolExecutor threadpool = new ThreadPoolExecutor(3, 3, 60L, TimeUnit.DAYS,                        new SynchronousQueue<Runnable>(), new ThreadFactory() {            private final AtomicInteger counter = new AtomicInteger(1);            @Override            public Thread newThread(final Runnable r) {                return new Thread(r, "thread-sn-" + counter.getAndIncrement());            }        });        threadpool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());        Thread.currentThread().setName("main-thread");        for (int i = 0; i < 15; ++i) {            threadpool.submit(new Runnable() {                @Override                public void run() {                    try {                        int timeout = 0;                        while ((timeout = random.nextInt()) <= 0) {                        }                        timeout = timeout % 111;                        Thread.sleep(timeout * 100L);                        lock.lock();                        callLongTime();                    }                    catch (final Exception e) {                        e.printStackTrace();                    }                    finally {                        lock.unlock();                    }                }            });        }        threadpool.shutdown();    }    public static void main(final String[] args) throws Exception {        new DeadLock().runWork();    }    static long callLongTime() {        System.out.println("thread name " + Thread.currentThread().getName());        long sum = 0;        for (long i = 0; i < 10000000000L; ++i) {            sum = sum ^ i + i;        }        return sum;    }}
最后一个样例,写的比较复杂,但事情比较简单,线程之间的同步关键字换成了Java5提供的concurrent库中的重入锁。

"thread-sn-3" prio=6 tid=0x02de8400 nid=0x2688 runnable [0x0320f000]   java.lang.Thread.State: RUNNABLEat DeadLock.callLongTime(DeadLock.java:63)at DeadLock$2.run(DeadLock.java:41)at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)at java.util.concurrent.FutureTask.run(FutureTask.java:166)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)at java.lang.Thread.run(Thread.java:722)   Locked ownable synchronizers:- <0x22be1488> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)- <0x22be66d8> (a java.util.concurrent.ThreadPoolExecutor$Worker)"thread-sn-2" prio=6 tid=0x02de6c00 nid=0x218c waiting on condition [0x031bf000]   java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for  <0x22be1488> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:867)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1197)at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:214)at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:290)at DeadLock$2.run(DeadLock.java:40)at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)at java.util.concurrent.FutureTask.run(FutureTask.java:166)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)at java.lang.Thread.run(Thread.java:722)   Locked ownable synchronizers:- <0x22be64e0> (a java.util.concurrent.ThreadPoolExecutor$Worker)
栈信息比较有趣,JVM在每个线程线的最后部分输出了当前线程持有的锁对象,类型和地址。从上述的信息中可以找到,当前处于运行状态的线程持有了一个地址为0x22be1488的锁,而这个锁正是其它线程等待获取的锁对象。

写在后面

简单的几个样例而已,不必纠结。重点是栈信息是有用的,栈信息并不复杂,但是需要一定的耐心。

家里有了宝宝,白天的时间都要抱小孩,码字的时间就少了,只好等晚上宝宝睡了,才有机会写写。

2013年2月11日2:03:57


原创粉丝点击