从堆栈角度看线程状态(7)

异常信息

见过很多这样的人,写代码、调式代码的过程中,程序一旦出现异常,就很着急的去清空日志输出控制台,清空的异常日志信息,然后尝试各种重启方式。异常一直出现就开始嘀咕了,刚才还好好的啊、以前没出现过这问题啊......
没有仔细认真的看异常是很多新手容易犯的错误,不少老手也会存在这样的问题,不知道你是否躺枪。其实,认真的看异常问题就解决了一半了。异常信息是我们排查问题的方向。对于没有异常的、或是把异常吃掉的这种程序问题,问题定位、排查是相对困难的,至少问题定位不会那么直接,可能会把整个业务条线、甚至对整个系统体系进行分析才能摸到问题产生的原因。
可见异常信息对问题的排查提供了可靠的依据,异常信息反应的是程序调用的整个过程,调了哪个方法、是哪个实现类、卡在什么地方了......
异常信息是线程执行过程中出现异常时,输出的线程堆栈信息,下面我们看看线程各种状态对应的堆栈信息。

线程堆栈信息解读

堆栈状态WAITING与TIMED_WAITING:

调用以下三个方法会进入WAITING状态:

  • Object.wait() 不设置超时时间
  • Thread.join() 不设置超时时间
  • LockSupport.park() 不设置超时时间

调用下面的方法会进入TIMED_WAITING状态:

  • Object.wait(time)
  • Thread.join(time)
  • Thread.sleep(time)
  • LockSupport.parkNanos(time)
  • LockSupport.parkUntil(time)
RUNNABLE和BLOCKED状态
  • 在执行中的线程处于RUNNABLE状态
  • 等待监视器同步锁的线程处于BLOCKED状态

堆栈信息中不存在NEW和TERMINATED状态

看代码示例:

package net.zkbc.p2p;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author HXJ
 * @date 2018/8/1
 */
public class ThreadStack {
    public static void main(String[] args) {
        final Object blocker = new Object();
        //运行状态的线程
        Thread threadRun = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    //do something ...
                }
            }
        }, "Thread-run");

        //同步阻塞状态的线程
        Thread threadBlock = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 同步锁阻塞 !");
                synchronized (blocker) {
                    System.out.println(Thread.currentThread().getName() + " 取得同步锁 run ... !");
                }
            }
        }, "Thread-block");

        //wait等待
        Thread threadWait = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName() + " wait ... !");
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
        }, "Thread-wait");

        //wait timeout 等待
        Thread threadWaitTimeout = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName() + " wait ... !");
                    try {
                        this.wait(10 * 1000 * 60);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }, "Thread-wait-timeout");

        //sleep 等待
        Thread threadSleepWaitTimeout = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + " wait ... !");
                    Thread.sleep(10 * 1000 * 60);
                } catch (InterruptedException e) {
                }
            }
        }, "Thread-sleep-wait-timeout");

        //lock.park 等待 为了显示相对应阻塞的堆栈信息
        Lock lock = new ReentrantLock();
        //lock获得锁后执行的线程
        Thread lockParkRun = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " lock-park run...");
                lock.lock();
                while (true) {
                    //do something ...
                }
            }
        }, "Thread-lock.park-run");
        //lock失败后park()阻塞的线程
        Thread lockPark = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " lock-park park ...");
                lock.lock();
            }
        }, "Thread-lock.park-park");

        try {
            synchronized (blocker) {
                System.out.println(threadRun.getName() + " 新建状态:" +threadRun.getState());
                threadRun.start();
                threadBlock.start();
                threadWait.start();
                threadWaitTimeout.start();
                threadSleepWaitTimeout.start();
                lockParkRun.start();
                lockParkRun.join(100);
                lockPark.start();
                //主线程不释放锁,并且通过等待阻塞线程的方式进行阻塞
                threadBlock.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

Thread-run 新建状态:NEW
Thread-block 同步锁阻塞 !
Thread-wait wait ... !
Thread-wait-timeout wait ... !
Thread-sleep-wait-timeout wait ... !
Thread-lock.park-run lock-park run...
Thread-lock.park-park lock-park park ...

代码中threadRun线程在start()之前,状态是NEW,启动后在堆栈信息中为RUNNABLE状态。threadRun是个空死循环,测试代码的时候注意自己的电脑哦!
主线程创建完其他线程后,获取blocker对象上的同步锁,然后分别启动其他线程。threadBlock线程启动后,获取blocker对象上的同步锁,由于该锁已经被主线程持有,所有进入BLOCKED阻塞状态。其他线程进入相应状态,最后避免主线程退出,直接让它等待在threadBlock线程监视器上,由于threadBlock无法获得同步锁而无法执行,至此在其他超时等待的线程超时前有如下堆栈信息(部分):

"Thread-lock.park-park" #18 prio=5 os_prio=0 tid=0x00000000201a1800 nid=0x70f0 waiting on condition [0x0000000020fcf000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x000000076bdf6070> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
    at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
    at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
    at net.zkbc.p2p.ThreadStack$7.run(ThreadStack.java:90)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None

"Thread-lock.park-run" #17 prio=5 os_prio=0 tid=0x000000002018c000 nid=0x7740 runnable [0x0000000020ecf000]
   java.lang.Thread.State: RUNNABLE
    at net.zkbc.p2p.ThreadStack$6.run(ThreadStack.java:81)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - <0x000000076bdf6070> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

"Thread-sleep-wait-timeout" #16 prio=5 os_prio=0 tid=0x000000002018b000 nid=0x7a78 waiting on condition [0x0000000020dcf000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at net.zkbc.p2p.ThreadStack$5.run(ThreadStack.java:68)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None

"Thread-wait-timeout" #15 prio=5 os_prio=0 tid=0x0000000020188800 nid=0x62d4 in Object.wait() [0x0000000020cce000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076bded8b8> (a net.zkbc.p2p.ThreadStack$4)
    at net.zkbc.p2p.ThreadStack$4.run(ThreadStack.java:55)
    - locked <0x000000076bded8b8> (a net.zkbc.p2p.ThreadStack$4)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None

"Thread-wait" #14 prio=5 os_prio=0 tid=0x0000000020187800 nid=0x759c in Object.wait() [0x0000000020bcf000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076bdead90> (a net.zkbc.p2p.ThreadStack$3)
    at java.lang.Object.wait(Object.java:502)
    at net.zkbc.p2p.ThreadStack$3.run(ThreadStack.java:41)
    - locked <0x000000076bdead90> (a net.zkbc.p2p.ThreadStack$3)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None

"Thread-block" #13 prio=5 os_prio=0 tid=0x0000000020150800 nid=0x4f70 waiting for monitor entry [0x0000000020acf000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at net.zkbc.p2p.ThreadStack$2.run(ThreadStack.java:29)
    - waiting to lock <0x000000076bb53ef0> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None

"Thread-run" #12 prio=5 os_prio=0 tid=0x000000002014f800 nid=0x4f04 runnable [0x00000000209cf000]
   java.lang.Thread.State: RUNNABLE
    at net.zkbc.p2p.ThreadStack$1.run(ThreadStack.java:17)
    at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
    - None

"main" #1 prio=5 os_prio=0 tid=0x00000000036ac800 nid=0x779c in Object.wait() [0x000000000369f000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076bb56140> (a java.lang.Thread)
    at java.lang.Thread.join(Thread.java:1252)
    - locked <0x000000076bb56140> (a java.lang.Thread)
    at java.lang.Thread.join(Thread.java:1326)
    at net.zkbc.p2p.ThreadStack.main(ThreadStack.java:106)
    - locked <0x000000076bb53ef0> (a java.lang.Object)

   Locked ownable synchronizers:
    - None

可以通过jstack命令获取堆栈信息文件,如:jstack -l pid > stack.log
我们可以清晰的从堆栈信息中看到每个线程当前的状态,新建、终止状态不在这里体现,也清晰的反应出程序调用的每一个过程。用好堆栈信息,很多问题便可迎刃而解!

Waiting for monitor entry 和 in Object.wait()

这两种阻塞情况是基于Monitor实现的,Monitor是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象有且仅有一个 monitor。如图:



从图中可以看出,每个 Monitor在任何时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitorentry”,而在 “Wait Set”中等待的线程状态是“in Object.wait()”。
通过使用关键字synchronized来使用监视器,synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。如:

synchronized(obj){ 
    //临界区 do something ...
} 

当线程获得了 Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。只有当别的线程在该对象上调用了 notify() 或者 notifyAll() , “ Wait Set”队列中线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。

synchronized(obj){ 
     ...
    //未满足执行条件 wait ... 等待obj.notify();
    obj.wait();
} 

waiting on condition

sleep()、LockSupport.park()都属于这种阻塞,等待条件满足。

线程资源(cpu、内存)占用高案例分析

示例:
在linux环境下,通过命令:top -Hp pid
获得如图:


取将十进制16275对应的线程pid转换为16进制为3F93
用命令printf或计算器:
命令行:
printf '%x \n' PID

计算器:
进制转换

堆栈查找到对应nid=0x3f93的线程:

...

"Apollo-ReleaseMessageServiceWithCache-1" #25 daemon prio=5 os_prio=0 tid=0x00007fc4e4009800 nid=0x3f94 waiting on condition [0x00007fc51cca2000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000d33338d8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
    at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - None

"Apollo-AppNamespaceServiceWithCache-1" #24 daemon prio=5 os_prio=0 tid=0x00007fc4e44b9800 nid=0x3f93 runnable [0x00007fc51cce3000]
   java.lang.Thread.State: TIMED_WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x00000000d33325e8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - None

"Apollo-DatabaseMessageSender-1" #23 daemon prio=5 os_prio=0 tid=0x00007fc4e4b01800 nid=0x3f92 sleeping[0x00007fc51cd24000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.ctrip.framework.apollo.biz.message.DatabaseMessageSender.lambda$initialize$0(DatabaseMessageSender.java:78)
    at com.ctrip.framework.apollo.biz.message.DatabaseMessageSender$$Lambda$2/1620636712.run(Unknown Source)
    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)

...

找到对应线程后,进而查看对应线程的具体堆栈信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容