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
- Java程序栈信息文件中的秘密(二)
- Java程序栈信息文件中的秘密(一)
- Java程序栈信息文件中的秘密(三)
- Java程序栈信息文件中的秘密(四)
- Java程序栈信息文件中的秘密(五)
- Java程序栈信息文件中的秘密(一)
- Java程序栈信息文件中的秘密(一)
- 用java程序读取xml文件中的配置信息
- Class文件中的秘密
- java中的字符串(String)的秘密
- java线程 --- 返回线程中的信息 (二)
- 秘密信息
- java中获取 .properties文件中的信息
- Win32下绘图程序(二)保存绘图信息至文件
- Android中的meminfo(二):用代码来读取文件信息
- java中的io——读写大文件(二)
- 信息在网络中的漫游(二)
- //获取程序文件信息
- UVa 748 - Exponentiation
- 岁末总结
- UVa 10106 - Product
- serializeObject——序列化Form表单到一个对象
- 基于阿里云服务器搭建SVN服务器
- Java程序栈信息文件中的秘密(二)
- 【新年新气象_新年CSDN第一篇】斯特林数简介及应用
- IP地址,子网掩码,地址池计算
- Java 求两个数最小公倍数
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- MyEclipse8.5安装aptana3.x插件(MyEclipse8.6版本也适用)
- MFC应用程序接收命令行参数的实现
- 关于main函数的标准形式
- Delphi TStringList的用法