zoukankan      html  css  js  c++  java
  • JDK的命令行工具系列 (三) jhat、jstack

    jhat: heapdump文件分析工具

    在前两篇系列文章JDK的命令行工具系列 (一) jps、jstatJDK的命令行工具系列 (二) javap、jinfo、jmap中, 我们已经介绍过了jpsjmap这些命令行工具的使用, 所以这里就不在多做说明, 直接演示jhat的使用。

    代码清单:

    public class JhatDemo {
        Integer i = 6;
        public static void main(String[] args) {
            String str = "qingshanli";
            while(true) {
                System.out.println(str);
            }
        }
    }

    命令行下:  

    //查看java进程的pid
    C:Usersliqingshan>jps -l 12816 14352 org.apache.catalina.startup.Bootstrap 15924 sun.tools.jps.Jps 14184 JhatDemo 14092 org.jetbrains.jps.cmdline.Launcher
    //生成heapdump文件, 文件名为JhatDemo_heapdump C:Usersliqingshan
    >jmap -dump:format=b,file=JhatDemo_heapdump 14184 Dumping heap to C:UsersliqingshanJhatDemo_heapdump ... Heap dump file created
    //解析heapdump文件, 并启动一个HTTP/HTML服务器, 默认端口号为7000 C:Usersliqingshan
    >jhat JhatDemo_heapdump Reading from JhatDemo_heapdump... Dump file created Mon Jul 16 20:17:19 GMT+08:00 2018 Snapshot read, resolving... Resolving 516681 objects... Chasing references, expect 103 dots....................................................................................................... Eliminating duplicate references....................................................................................................... Snapshot resolved. Started HTTP server on port 7000 Server is ready.

    在浏览器中输入地址: http://localhost:7000, 就可以在页面查看堆转储快照详细信息。

    其中Show heap histogram选项可以查看堆的对象数量、内存大小、类的全限定名。

    jstack: Java堆栈跟踪工具

    由于在网上已经有关于jstack的优质博客文章, 所以这里就不重复造轮子了, 下面所有有关jstack的内容均转载自 Java命令学习系列(二)——Jstack-HollisChuang's Blog

    概述

    jstack命令用于生成虚拟机当前时刻的线程快照(也叫thread dump文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

    Monitor

    在多线程的JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。下面这个图,描述了线程和 Monitor之间关系,以 及线程的状态转换图:

    进入区(Entry Set): 表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则冲入拥有者;否则则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。

    拥有者(The Owner): 表示某一线程成功竞争到对象锁。

    等待区(Wait Set): 表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。

    从图中可以看出,一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “BLOCKED”,而在 “Wait Set”中等待的线程状态是 “WAITING"或"TIMED_WAITING”。 先看 “Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。

    线程动作 (线程状态产生的原因)

    • runnable: 状态一般为RUNNABLE。
    • in Object.wait(): 等待区等待,状态为WAITING或TIMED_WAITING。
    • waiting for monitor entry: 进入区等待,状态为BLOCKED。
    • waiting on condition: 等待区等待、被park。等待某个资源或条件发生来唤醒自己。
    • sleeping: 休眠的线程,调用了Thread.sleep()。

    线程状态

    • NEW: 未启动的。不会出现在Dump中。
    • RUNNABLE: 在虚拟机内执行的。
    • BLOCKED: 受阻塞并等待监视器锁。
    • WATING: 无限期等待另一个线程执行特定操作。
    • TIMED_WATING: 有时限的等待另一个线程的特定操作。
    • TERMINATED: 已退出的。

    调用修饰

    调用修饰表示线程在调用方法时额外的重要操作, 它是线程dump分析的重要工具, 用来修饰方法调用。

    • locked <地址> 目标:使用synchronized申请对象锁成功,监视器的拥有者。
    • waiting to lock <地址> 目标:使用synchronized申请对象锁未成功,在冲入区等待。
    • waiting on <地址> 目标:使用synchronized申请对象锁成功后,释放锁并在等待区等待。
    • parking to wait for <地址> 目标

    locked

    at oracle.jdbc.driver.PhysicalConnection.prepareStatement
    - locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
    at oracle.jdbc.driver.PhysicalConnection.prepareStatement
    - locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
    at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement

    通过synchronized关键字,成功获取到了对象的锁,成为监视器的拥有者,在临界区内操作。对象锁是可以线程重入的

    waiting to lock

    at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
    - waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
    at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
    at com.jiuqi.dna.core.impl.ContextImpl.find
    at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo

    通过synchronized关键字,没有获取到对象的锁,线程在监视器的冲入区等待。在调用栈顶出现,线程状态为Blocked。

    waiting on

    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000da2defb0> (a WorkingThread)
    at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
    - locked <0x00000000da2defb0> (a WorkingThread)
    at com.jiuqi.dna.core.impl.WorkingThread.run

    通过synchronized关键字,成功获取到了对象的锁后,调用了wait方法,进入对象的等待区等待。在调用栈顶出现,线程状态为WAITING或TIMED_WATING。

    parking to wait for

    park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包会出现的新的机制, 和synchronized体系不同。

    线程dump分析

    冲入区等待

    "d&a-3588" daemon waiting for monitor entry [0x000000006e5d5000] //线程动作,冲入区等待
    java.lang.Thread.State: BLOCKED (on object monitor) //线程状态
    at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()
    - waiting to lock <0x0000000602f38e90> (a java.lang.Object) //方法调用修饰
    at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()

    线程状态BLOCKED,线程动作wait on monitor entry,调用修饰waiting to lock总是一起出现。表示在代码级别已经存在冲突的调用。必然有问题的代码,需要尽可能减少其发生。

    同步块阻塞

    "blocker" runnable //线程动作
    java.lang.Thread.State: RUNNABLE //线程状态
    at com.jiuqi.hcl.javadump.Blocker$1.run(Blocker.java:23)
    - locked <0x00000000eb8eff68> (a java.lang.Object) //方法调用修饰
    "blockee-11" waiting for monitor entry  //线程动作,冲入区等待
    java.lang.Thread.State: BLOCKED (on object monitor) //线程状态
    at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
    - waiting to lock <0x00000000eb8eff68> (a java.lang.Object) //方法调用修饰
    "blockee-86" waiting for monitor entry //线程动作, 冲入区等待
    java.lang.Thread.State: BLOCKED (on object monitor) //线程状态, 阻塞
    at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41) 
    - waiting to lock <0x00000000eb8eff68> (a java.lang.Object) //方法调用修饰

    一个线程锁住某对象,大量其他线程在该对象上等待。

    持续运行的IO

    "d&a-614" daemon prio=6 tid=0x0000000022f1f000 nid=0x37c8 runnable //线程动作
    [0x0000000027cbd000]
    java.lang.Thread.State: RUNNABLE //线程状态
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(Unknown Source)
    at oracle.net.ns.Packet.receive(Packet.java:240)
    at oracle.net.ns.DataPacket.receive(DataPacket.java:92)
    at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:172)
    at oracle.net.ns.NetInputStream.read(NetInputStream.java:117)
    at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1034)
    at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:588)

    IO操作是可以以RUNNABLE状态达成阻塞。例如:数据库死锁、网络读写。 格外注意对IO线程的真实状态的分析。 一般来说,被捕捉到RUNNABLE的IO调用,都是有问题的。

    以上堆栈显示: 线程状态为RUNNABLE。 调用栈在SocketInputStream或SocketImpl上,socketRead0等方法。 调用栈包含了jdbc相关的包。很可能发生了数据库死锁。

    分线程调度的休眠

    正常的线程池等待

    "d&a-131" in Object.wait() //线程动作
    java.lang.Thread.State: TIMED_WAITING (on object monitor) //线程状态
    at java.lang.Object.wait(Native Method)
    at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo(WorkingManager.java:322)
    - locked <0x0000000313f656f8> (a com.jiuqi.dna.core.impl.WorkingThread) //方法调用修饰,使用synchronized申请对象锁成功,监视器的拥有者
    at com.jiuqi.dna.core.impl.WorkingThread.run(WorkingThread.java:40)

    可疑的线程等待

    "d&a-121" in Object.wait() //线程动作
    java.lang.Thread.State: WAITING (on object monitor) //线程状态
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:485)
    at com.jiuqi.dna.core.impl.AcquirableAccessor.exclusive()
    - locked <0x00000003011678d8> (a com.jiuqi.dna.core.impl.CacheGroup) //方法调用修饰,使用synchronized申请对象锁成功,监视器的拥有者
    at com.jiuqi.dna.core.impl.Transaction.lock()

    命令格式

    C:Usersliqingshan>jstack -h
    Usage:
        jstack [-l] <pid>
            (to connect to running process)
        jstack -F [-m] [-l] <pid>
            (to connect to a hung process)
        jstack [-m] [-l] <executable> <core>
            (to connect to a core file)
        jstack [-m] [-l] [server_id@]<remote server IP or hostname>
            (to connect to a remote debug server)
    
    Options:
        -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
        -m  to print both java and native frames (mixed mode)
        -l  long listing. Prints additional information about locks
        -h or -help to print this help message

    死锁代码清单:

    public class JStackDemo {
        public static void main(String[] args) {
            Thread t1 = new Thread(new DeadLockclass(true));
            Thread t2 = new Thread(new DeadLockclass(false));
            t1.start();
            t2.start();
        }
    }
    class DeadLockclass implements Runnable { public boolean falg; DeadLockclass(boolean falg) { this.falg = falg; } public void run() { if (falg) { while (true) { synchronized (Suo.o1) { System.out.println("o1 " + Thread.currentThread().getName()); synchronized (Suo.o2) { System.out.println("o2 " + Thread.currentThread().getName()); } } } } else { while (true) { synchronized (Suo.o2) { System.out.println("o2 " + Thread.currentThread().getName()); synchronized (Suo.o1) { System.out.println("o1 " + Thread.currentThread().getName()); } } } } } }
    class Suo { static Object o1 = new Object(); static Object o2 = new Object(); }

    jstack命令查看堆栈信息

    javac编译、java运行, 如下程序运行中产生死锁: 

    jps -l 查看java进程pid15548, jstack -l 15548 查看线程堆栈信息, 部分截图如下: 

    补充

    虚拟机执行Full GC时,会阻塞所有的用户线程。因此,即时获取到同步锁的线程也有可能被阻塞。 所以在查看线程Dump时, 应先查看内存使用情况。

    参考资料

    Java同步机制之Monitor监视器与syncrhoized实现原理

    jstack日志深入理解

    Java命令学习系列(五)——jhat-HollisChuang's Blog

    Java命令学习系列(二)——Jstack-HollisChuang's Blog

    Java多线程:  synchronized的可重入性

    《深入理解Java虚拟机》

    作者:张小凡
    出处:https://www.cnblogs.com/qingshanli/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。

  • 相关阅读:
    linux sysfs (2)
    微软——助您启动云的力量网络虚拟盛会
    Windows Azure入门教学系列 全面更新啦!
    与Advanced Telemetry创始人兼 CTO, Tom Naylor的访谈
    Windows Azure AppFabric概述
    Windows Azure Extra Small Instances Public Beta版本发布
    DataMarket 一月内容更新
    和Steve, Wade 一起学习如何使用Windows Azure Startup Tasks
    现实世界的Windows Azure:与eCraft的 Nicklas Andersson(CTO),Peter Löfgren(项目经理)以及Jörgen Westerling(CCO)的访谈
    正确使用Windows Azure 中的VM Role
  • 原文地址:https://www.cnblogs.com/qingshanli/p/9318783.html
Copyright © 2011-2022 走看看