JVM性能监控与调优(二)诊断工具-命令行 jps jstat jinfo jmap jhat jstack

目录

概述

命令行工具

javac , java

jps:查看正在运行的 Java 进程

基本用法

options 参数

-q:仅仅显示本地虚拟机唯一 id。不显示名称。

-l:输入应用程序主类的全类名或如果执行的是 jar 包,则输出 jar 完整路径。

-m:输出虚拟机进程启动时传递给主类 main()的参数

-v:列出虚拟机进程启动时的 JVM 参数。比如:-Xms20m -Xmx50m

hostid 参数

 

jstat:查看 JVM 统计信息

基本语法

option:

-t 参数

-interval 参数

count 参数

-h 参数


概述

性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。

Java 作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。可能造成 Java 应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络 I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。

命令行工具

javac , java

刚接触 java 学习的时候,大家肯定最先了解的两个命令就是javac , java

那么 , 除此之外,还有没有其他的命令可以供我们使用呢?

我们进入到安装 jdk 中的 bin 目录,发现还有一系列辅助工具。这些辅助工具用来获取目标 JVM 不同方面、不同层次的信息,帮助开发人员很好地解决 Java 应用程序的一些疑难杂症。

jps:查看正在运行的 Java 进程

jps(Java Process Status ):查询正在运行的Java进程。

测试:

 public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        System.out.println(next);
    }

运行起来之后,我们在命令行输入 jps 可以查看到该进程 id 以及名称。

对于本地虚拟机进程来说,进程的本地虚拟机 ID与操作系统的进程 ID是一致的,是唯一的。

基本用法

语法格式:

jps [options] [hostid]

options 参数

-q:仅仅显示本地虚拟机唯一 id。不显示名称。

jps -q

-l:输入应用程序主类的全类名或如果执行的是 jar 包,则输出 jar 完整路径。

jps -l

-m:输出虚拟机进程启动时传递给主类 main()的参数

jps -m

-v:列出虚拟机进程启动时的 JVM 参数。比如:-Xms20m -Xmx50m

jps -v

如果 Java 进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData) , 那么 jps 命令以及 jstat 将无法获取该 Java 进程。

hostid 参数

RMI 注册表中注册的主机名。

如果想要远程监控主机上的 java 程序,需要安装 jstatd。

对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到 IP 地址欺诈攻击。

如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行 jstatd 服务器,而是在本地使用 jstat 和 jps 工具。

 

jstat:查看 JVM 统计信息

jstat(JVM Statistics Monitoring Tool): 用于监视虚拟机各种运行状态信息的命令行工具。

它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。

在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期间定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。
 

 

 

 

基本语法

基本语法为:

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

查看命令相关参数:

jstat -h 或者 jstat -help

option

类相关的:

  • class   显示 ClassLoader 的相关信息:类的装载卸载数量总空间类装载所消耗的时间

垃圾回收相关的:

  • gc
  • gccapacity
  • gcutil
  • gccause
  • gcnew
  • gcnewcapacity
  • gcold
  • gcoldcapacity
  • gcmetacapacity

JIT 相关的:

  • compiler:显示 JIT 编译器编译过的方法、耗时等信息
  • printcompilation:输出已经被 JIT 编译的方法

 

先以class为例,讲解各个参数:

-t 参数

可以在输出信息前加上一个 Timestamp 列,显示程序一共运行多长时间。单位:秒。

jstat -class -t PID

-interval 参数

用于指定输出统计数据的周期,单位为毫秒。即:查询间隔。

jstat -class PID ms

count 参数

用于指定查询的总次数,n 为总次数

jstat -class PID ms n

-h 参数

可以在周期性数据输出时,输出多少行数据后输出一个表头信息。

 

option各个讲解:

-class  显示 ClassLoader 的相关信息:类的装载卸载数量总空间类装载所消耗的时间

jstat -class -t -h3 10260 1000 6

其中Timestamp代表程序至今的运行时间,单位为秒;Loaded代表加载的类的数目;Bytes代表加载的类的总字节数;Unloaded代表卸载的类的数目;Bytes代表卸载的类的总字节数;Time代表类装载所消耗的时间;

 

/**
 * -Xms60m -Xmx60m -XX:SurvivorRatio=8
 * 堆的最小 最大
 * SurvivorRatio,它定义了新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10
 */
public class JstatTest {

    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<byte[]>();

        for (int i = 0; i <1000 ; i++) {
            byte[] b = new byte[1024*100];//100k
            list.add(b);
            try {
                Thread.sleep(120);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

-gc:显示与 GC 相关的堆信息。包括 Eden 区、两个 Survivor 区、老年代、永久代等的容量、已用空间、GC 时间合计等信息。

jstat -gc  14516 1000

参数解释:

新生代相关

  • SOC 是第一个幸存者区的大小(字节)
  • S1C 是第二个幸存者区的大小(字节)
  • SOU 是第一个幸存者区已使用的大小(字节)
  • S1U 是第二个幸存者区已使用的大小(字节)
  • EC 是 Eden 空间的大小(字节)
  • EU 是 Eden 空间已使用大小(字节)

老年代相关

  • OC 是老年代的大小(字节)
  • OU 是老年代已使用的大小(字节)

方法区(元空间)相关

  • MC 是方法区的大小
  • MU 是方法区已使用的大小
  • CCSC 是压缩类空间的大小
  • CCSU 是压缩类空间已使用的大小

其它

  • YGC 是指从应用程序启动到采样时 young gc 次数
  • YGCT 是指从应用程序启动到采样时 young gc 消耗的时间(秒)
  • FGC 是指从应用程序启动到采样时 full gc 次数
  • FGCT 是指从应用程序启动到采样时 full gc 消耗的时间(秒)
  • GCT 是指从应用程序启动到采样时 gc 的总时间
     

-gcutil:显示内容与 -gc 基本相同,但输出主要关注已使用空间占总空间的百分比

jstat -gcutil PID

-gccapacity :显示内容与-gc基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间

jstat -gccapacity 15012

-gccause:与 -gcutil 功能一样,但是会额外输出导致最后一次或当前正在发生的 GC 产生的原因。

jstat -gccause PID

-gcnew显示新生代 GC 状况

jstat -gcnew PID

  • S0C:第一个 survivor 区大小
  • S1C:第二个 survivor 区的大小
  • S0U:第一个 survivor 区的使用大小
  • S1U:第二个 survivor 区的使用大小
  • TT:对象在新生代存活的次数
  • MTT:对象在新生代存活的最大次数
  • DSS:期望的 survivor 区大小
  • EC:eden 区的大小
  • EU:eden 区的使用大小
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间
     

-gcnewcapacity:显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间。

jstat -gcnewcapacity PID

  • NGCMN:新生代最小容量
  • NGCMX:新生代最大容量
  • NGC:当前新生代容量
  • S0CMX:最大 survivor1 区大小
  • S0C:当前 survivor1 区大小
  • S1CMX:最大 survivor2 区大小
  • S1C:当前 survivor2 区大小
  • ECMX:最大 eden 区大小
  • EC:当前 eden 区大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代回收次数
     

-gcold: 显示老年代 GC 状况。

jstat -gcold PID

  • MC:方法区大小
  • MU:方法区使用大小
  • CCSC:压缩类空间大小
  • CCSU:压缩类空间使用大小
  • OC:老年代大小
  • OU:老年代使用大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

-gcoldcapacity:老年代内存统计,主要关注使用到的最大、最小空间

jstat -gcoldcapacity PID

  • OGCMN:老年代最小容量
  • OGCMX:老年代最大容量
  • OGC:当前老年代大小
  • OC:老年代大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

-gcmetacapacity:输出永久代使用到的最大、最小空间

  • MCMN:最小元数据容量
  • MCMX:最大元数据容量
  • MC:当前元数据空间大小
  • CCSMN:最小压缩类空间大小
  • CCSMX:最大压缩类空间大小
  • CCSC:当前压缩类空间大小
  • YGC:年轻代垃圾回收次数
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

 

-compiler 显示 JIT 编译器编译过的方法、耗时等信息。

jstat -compiler 10260

-printcompilation 输出已经被 JIT 编译的方法。

jstat -printcompilation 10260

被编译过的方法 length

 

jstat 还可以用来判断是否出现内存泄漏 :

第 1 步:

在长时间运行的 Java 程序中,我们可以运行 jstat 命令连续获取多行性能数据,并取这几行数据中 OU 列(即已占用的老年代内存)的最小值。

第 2 步:

然后,我们每隔一段较长的时间重复一次上述操作,来获得多组 OU 最小值。
如果这些值呈上涨趋势,则说明该 Java 程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。
 

jinfo:实时查看和修改 JVM 配置参数

jinfo(Configuralion Info for Java) : 查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。

在很多情况下,Java 应用程序不会指定所有的 Java 虚拟机参数。而此时,开发人员可能不知道某一个具体的 Java 虚拟机参数的默认值。

在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了 jinfo 工具,开发人员可以很方便地找到 Java 虚拟机参数的当前值。
 

基本语法

jinfo [ options ] pid

[options]:

选项选项说明
no option输出全部的参数和系统属性
-flag name输出对应名称的参数
-flag [±]name开启或者关闭对应名称的参数只有被标记为 manageable 的参数才可以被动态修改
-flag name=value设定对应名称的参数
-flags输出全部的参数
-sysprops输出系统属性

查看

-sysprops可以查看由 System.getProperties()取得的参数。

jinfo -sysprops PID

-flags:查看曾经赋过值的一些参数。

jinfo -flags PID

 

D:\>jinfo -flags 13740

JVM version is 25.191-b12
Non-default VM flags:
-XX:CICompilerCount=3 -XX:InitialHeapSize=62914560
-XX:MaxHeapSize=62914560 -XX:MaxNewSize=20971520 
-XX:MinHeapDeltaBytes=524288 -XX:NewSize=20971520 
-XX:OldSize=41943040 -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers
-XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps 
-XX:-UseLargePagesIndividualAllocation 
-XX:+UseParallelGC
Command line:  -Xms60m -Xmx60m -XX:SurvivorRatio=8 
-javaagent:D:\software\IntelliJIDEA2019\lib\idea_rt.jar=14300:D:\software\IntelliJIDEA2019\bin -Dfile.encoding=UTF-8

-flag查看某个 java 进程具体参数

 jinfo -flag 具体参数 PID

表示使用了并行垃圾回收器

没有使用G1回收器

 

修改

jinfo 不仅可以查看运行时某一个 Java 虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。

但是,并非所有参数都支持动态修改。参数只有被标记为 manageable 的 flag 可以被实时修改。其实,这个修改能力是极其有限的。

可以查看被标记为 manageable 的参数。
 

java -XX:+PrintFlagsFinal -version | grep manageable
intx CMSAbortablePrecleanWaitMillis=100{manageable}
intx CMSWaitDuration=2000{manageable}
bool HeapDumpAfterFullGC=false{manageable}
bool HeapDumpBeforeFullGC=false{manageable}
bool HeapDumpOnOutofMemoryError=false{manageable}
ccstr HeapDumpPath {manageable}
uintx MaxHeapFreeRatio=100{manageable}
uintx MinHeapFreeRatio=0{manageable}
bool PrintClassHistogram=false{manageable}
bool PrintClassHistogragAfterFullGC=false{manageable}
bool PrintClassHistogramBeforeFullGC=false{manageable}
bool PrintConcurrentLocks=false{manageable}
bool PrintGC=false{manageable}
bool PrintGCDateStamps=false{manageable}
bool PrintGCDetails=false[manageable}
bool PrintGCTimeStamps=false{manageable}

 

针对 boolean 类型:

jinfo -flag [+-]具体参数 PID

查看PrintGCDetails这个参数的情况

修改:jinfo -flag  +PrintGCDetails 6740

 

针对非 boolean 类型:

jinfo -flag 具体参数=具体参数值 PID

扩展

  • java -XX+PrintFlagslnitial PID:查看所有 JVM 参数启动的初始值。
  • java -XX:+PrintFlagsFinal PID:查看所有 JVM 参数的最终值。
  • java -XX+PrintCommandLineFlags PID:查看已经被用户或者 JVM 设置过的详细参数名称和值
     

jmap:导出内存映像文件/内存使用情况

jmap(JVM Memory Map) : 作用一方面是获取 dump 文件(堆转储快照文件,二进制文件),它还可以获取目标 Java 进程的内存相关信息,包括 Java堆各区域的使用情况堆中对象的统计信息类加载信息等。

基本语法

jmap [option] <pid>
jmap [option] <executable <core>
jmap [option] [server_id@]<remote server IP or hostname>

其中 option 包括:

选项作用
-dump生成 dump 文件,-dump:live 只保存堆中存活的对象
-heap输出整个堆空间的详细信息,包括 GC 的使用、堆配置信息,以及内存的使用信息等
-histo输出堆空间中对象的统计信息,包括类、实例数量和合计容量
-permstat以 ClassLoader 为统计口径输出永久代的内存状态信息(仅 linux/solaris 平台有效)
-finalizerinfo显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象(仅 linux/solaris 平台有效)
-F当虚拟机进程对-dump 选项没有任何响应时,强制执行生成 dump 文件(仅 linux/solaris 平台有效)
  

说明:这些参数和 linux 下输入显示的命令多少会有不同,包括也受 jdk 版本的影响。

生成 Java 堆转储快照文件:dump

一般来说,使用 jmap 指令生成 dump 文件的操作算得上是最常用的 jmap 命令之一,将堆中所有存活对象导出至一个文件之中。

Heap Dump 又叫做堆存储文件,指一个 Java 进程在某个时间点的内存快照。Heap Dump 在触发内存快照的时候会保存此刻的信息如下:

All Object’s
Class, fields, primitive values and references

All Classes
ClassLoader, name, super class, static fields

Garbage Collection Roots
Objects defined to be reachable by the JVM

Thread Stacks and Local Variables
The call-stacks of threads at the moment of the snapshot, and per-frame information about local objects

说明:

  1. 通常在写 Heap Dump 文件前会触发一次 Full GC , 所以 Heap Dump 文件里保存的都是 Full GC 后留下的对象信息。
  2. 由于生成 dump 文件比较耗时,大家需要耐心等待,尤其是大内存镜像生成的 dump 文件则需要耗费更长的时间来完成。

手动的方式

jmap -dump:format=b,file=<filename.hprof><pid>
jmap -dump:live,format=b,file=<filename.hprof><pid> //存活对象
jmap -dump:format=b,file=d:1.hprof 20464

生成的文件:

 

自动的方式

当程序发生 OOM 退出系统时,一些瞬时信息都随着程序的终止而消失,而重现 OOM 问题往往比较困难或者耗时。
此时若能在 OOM 时,自动导出 dump 文件就显得非常迫切。

这里介绍一种比较常用的取得堆快照文件的方法,即使用:

在程序发生 OOM 时,导出应用程序的当前堆快照 :

-XX:+HeapDumpOnOutOfMemoryError

可以指定堆快照的保存位置

-XX:HeapDumpPath=<filename.hprof>

设置:

显示堆内存相关信息

查看各区大小

jmap -heap pid

所有类型使用的内存

jmap -histo pid

由于 jmap 将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap 需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。
也就是说,由 jmap 导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。

举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么 :live 选项将无法探知到这些对象。

另外,如果某个线程长时间无法跑到安全点, jmap 将一直等下去。
与前面讲的 jstat 则不同 , 垃圾回收器会主动将 jstat 所需要的摘要数据保存至固定位置之中, jstat 只需直接读取即可。

 

jhat:JDK 堆分析工具


jhat(JVM Heap Analysis Topl) :

Sun JDK 提供的jhat命令与 jmap 命令搭配使用,用于分析 jmap 生成的 heap dump 文件(堆转储快照)。
jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。

使用了 jhat 命令,就启动了一个 http 服务,端口是 7000 , 即 http://localhost:7000/ , 就可以在浏览器里分析。

说明:jhat 命令在 JDK9、JDK10 中已经被删除,官方建议用 VisualVM 代替。

基本语法

jhat [option] [dumpfile]

之后我们访问 localhost:7000

option 参数

参数含义
-stack false|true关闭|打开对象分配调用栈跟踪
-refs false|true关闭|打开对象引用跟踪
-port port-number设置 jhat http 端口号 默认 7000
-exclude exclude-file执行对象查询时需要排除的数据成员列表文件
-baseline exclude-file制定一个基准堆转储
-debug int设置 debug 级别
-version启动后显示版本信息后就退出
-J传入启动参数,比如 -J -Xmx512m

 

jstack:打印 JVM 中线程快照

jstack(JVM stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。

线程快照 : 当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。

生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁死循环请求外部资源导致的长时间等待等问题。

这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用 jstack 显示各个线程调用的堆栈情况。

在 thread dump 中,要留意下面几种状态:

  • 死锁Deadlock
  • 等待资源,Waiting on condition
  • 等待获取监视器Waiting on monitor entry
  • 阻塞Blocked
  • 执行中,Runnable
  • 暂停,Suspended
  • 对象等待中,Object.wait()TIMED_WAITING
  • 停止,Parked
     

基本语法

jstack [option] pid

jstack 管理远程进程的话,需要在远程程序的启动参数中增加:

  • Djava.rmi.server.hostname=……
  • Dcom.sun.management.jmxremote
  • Dcom.sun.management.jmxremote.port=8888
  • Dcom.sun.management.jmxremote.authenticate=false
  • Dcom.sun.management.jmxremote.ssl=false
     

演示死锁定位情况:

public static void main(String[] args) {
        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();

        Thread thread1 = new Thread("线程1") {
            @Override
            public void run() {

                synchronized (s1){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        System.out.println("我是"+Thread.currentThread().getName());
                    }
                }
            }
        };

        Thread thread2 = new Thread("线程2") {
            @Override
            public void run() {
                synchronized (s2){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        System.out.println("我是"+Thread.currentThread().getName());
                    }
                }

            }
        };

        thread1.start();
        thread2.start();

    }

运行程序,发现程序一直运行(因为咱们写了一个死锁)

用jstack查看:

jstack 22928

 查看结果:

D:\>jstack 22928
2021-05-05 10:02:21
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000002893800 nid=0x958 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"线程2" #12 prio=5 os_prio=0 tid=0x0000000018966000 nid=0x4520 waiting for monitor entry [0x000000001964f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.bufan.line.JstackTest$2.run(JstackTest.java:36)
        - waiting to lock <0x00000000d5ec56e8> (a java.lang.StringBuilder)
        - locked <0x00000000d5ec5730> (a java.lang.StringBuilder)

"线程1" #11 prio=5 os_prio=0 tid=0x0000000018963800 nid=0x8e4 waiting for monitor entry [0x000000001954f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.bufan.line.JstackTest$1.run(JstackTest.java:20)
        - waiting to lock <0x00000000d5ec5730> (a java.lang.StringBuilder)
        - locked <0x00000000d5ec56e8> (a java.lang.StringBuilder)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x0000000018903000 nid=0x487c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000000018898800 nid=0x3650 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000018896800 nid=0x24c4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000018892800 nid=0x4dbc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000018891000 nid=0x595c runnable [0x0000000018f4e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d5f0fe68> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d5f0fe68> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001753e800 nid=0x4e38 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000018858800 nid=0x3ebc runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000002986000 nid=0x57dc in Object.wait() [0x000000001884f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5d88ed0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x00000000d5d88ed0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000002983000 nid=0x560c in Object.wait() [0x000000001874f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5d86bf8> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d5d86bf8> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x00000000174a7800 nid=0x41d4 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000028a8800 nid=0x201c runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000028aa800 nid=0x5390 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000028ac000 nid=0x47b4 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000028ad800 nid=0x402c runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001895e000 nid=0x5894 waiting on condition

JNI global references: 12


Found one Java-level deadlock:
=============================
"线程2":
  waiting to lock monitor 0x000000000298b678 (object 0x00000000d5ec56e8, a java.lang.StringBuilder),
  which is held by "线程1"
"线程1":
  waiting to lock monitor 0x000000000298cb18 (object 0x00000000d5ec5730, a java.lang.StringBuilder),
  which is held by "线程2"

Java stack information for the threads listed above:
===================================================
"线程2":
        at com.bufan.line.JstackTest$2.run(JstackTest.java:36)
        - waiting to lock <0x00000000d5ec56e8> (a java.lang.StringBuilder)
        - locked <0x00000000d5ec5730> (a java.lang.StringBuilder)
"线程1":
        at com.bufan.line.JstackTest$1.run(JstackTest.java:20)
        - waiting to lock <0x00000000d5ec5730> (a java.lang.StringBuilder)
        - locked <0x00000000d5ec56e8> (a java.lang.StringBuilder)

Found 1 deadlock.

可以查看到线程1和线程2的线程状态时BLOCKED,打印信息中明确表明发现死锁:Found one Java-level deadlock,线程2等待的锁被线程1持有,线程1等待的锁被线程2持有,都在等待对方释放锁,同时也列出了相关信息,包括锁发生的位置程序的 36行和20行

 

option 参数

参数含义
-F当正常输出的请求不被响应时,强制输出线程堆栈
-I除堆栈外,显示关于锁的附加信息
-m如果调用到本地方法,可以显示 C/C++的堆栈
-h帮助操作
jstack -l 2152

两个文件对比:

 

jcmd:多功能命令行

在 JDK 1.7 以后,新增了一个命令行工具 jcmd 。
它是一个多功能的工具,可以用来实现前面除了 jstat 之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java 进程、导出线程信息、执行GC、JVM 运行时间等。

jcmd 拥有 jmap 的大部分功能,并且在 Oracle 的官方网站上也推荐使用 jcmd 命令代 jmap 命令。

 

基本语法

jcmd -h

jcmd:列出所有的 JVM 进程

jcmd -l:列出所有的 JVM 进程,和jps指令类似

jcmd pid help:针对指定的进程,列出支持的所有命令

jcmd pid 具体命令:显示指定进程的指令命令数据。

举例:

打印线程的信息,和 jstack 22292命令类似

jcmd 22292 Thread.print
D:\>jcmd 22292 Thread.print
22292:
2021-05-05 11:21:13
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x00000000035c3800 nid=0x12dc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"线程2" #12 prio=5 os_prio=0 tid=0x0000000019695000 nid=0x3e8 waiting for monitor entry [0x000000001a37e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.bufan.line.JstackTest$2.run(JstackTest.java:36)
        - waiting to lock <0x00000000d5ec5a10> (a java.lang.StringBuilder)
        - locked <0x00000000d5ec5a58> (a java.lang.StringBuilder)

"线程1" #11 prio=5 os_prio=0 tid=0x0000000019692800 nid=0x21b4 waiting for monitor entry [0x000000001a27e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.bufan.line.JstackTest$1.run(JstackTest.java:20)
        - waiting to lock <0x00000000d5ec5a58> (a java.lang.StringBuilder)
        - locked <0x00000000d5ec5a10> (a java.lang.StringBuilder)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000001965e000 nid=0x534c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000000195c7800 nid=0x2024 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000000195c6800 nid=0x5950 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000000195c2800 nid=0x51d0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000000195c0800 nid=0x4d64 runnable [0x0000000019c7e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d5f0fb18> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d5f0fb18> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001826e800 nid=0x57d8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019588800 nid=0x54ac runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x00000000036bb800 nid=0x5930 in Object.wait() [0x000000001957f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5d88ed0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x00000000d5d88ed0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000036b3000 nid=0x5bf8 in Object.wait() [0x000000001947e000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5d86bf8> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d5d86bf8> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x00000000181d7800 nid=0x4d34 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000035d8800 nid=0x5c80 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000035da800 nid=0x5a58 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000035dc000 nid=0x5af0 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000035dd800 nid=0x5248 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001968d800 nid=0x5b0c waiting on condition

JNI global references: 12


Found one Java-level deadlock:
=============================
"线程2":
  waiting to lock monitor 0x00000000036bb688 (object 0x00000000d5ec5a10, a java.lang.StringBuilder),
  which is held by "线程1"
"线程1":
  waiting to lock monitor 0x00000000036ba298 (object 0x00000000d5ec5a58, a java.lang.StringBuilder),
  which is held by "线程2"

Java stack information for the threads listed above:
===================================================
"线程2":
        at com.bufan.line.JstackTest$2.run(JstackTest.java:36)
        - waiting to lock <0x00000000d5ec5a10> (a java.lang.StringBuilder)
        - locked <0x00000000d5ec5a58> (a java.lang.StringBuilder)
"线程1":
        at com.bufan.line.JstackTest$1.run(JstackTest.java:20)
        - waiting to lock <0x00000000d5ec5a58> (a java.lang.StringBuilder)
        - locked <0x00000000d5ec5a10> (a java.lang.StringBuilder)

Found 1 deadlock.

 

 打印进程相关的柱状图:

jcmd 22292 GC.class_histogram

 可以代替一下命令:

jmap -histo 22292

导出堆信息:

jcmd 22292 GC.heap_dump d:33.hprof
jmap -dump:format=b,file=d:77.hprof 22292

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值