JVM 性能监控与故障处理工具

本文详细介绍JDK自带的六种监控工具:jps、jinfo、jmap、jstack、jstat和HSDIS,包括它们的功能、命令格式、参数说明及使用案例,帮助开发者在生产环境中快速定位和解决问题。

定位系统问题的时候,需要依据系统输出的数据信息查看具体原因,当然需要借助相应的工具以方便处理数据信息。

这里的数据信息包括:运行日志、GC 日志、异常堆栈、线程快照(threaddump/javacore 文件)、堆转储快照(heapdump/hprof文件)等。

JDK 的bin目录中提供了很多命令行工具,如我们熟悉的“java.exe”、“javac.exe”,其他命令参考bin目录。这些工具基本上是 lib/tools.jar 类库的再封装,主要功能代码在 tools 类库中实现。

这些监控工具,尤其是对已部署在生产环境上的应用程序排错是非常有帮助的:当应用程序部署到生产环境后,无论是能够直接接触服务器还是远程到服务器上都可能会受到限制,借助 JDK 监控工具,我们可以实现强大的监控分析功能。

下面主要介绍6种 JDK 监控和故障处理工具:

(1) jps:JVM Process Status Tool,显示指定系统内所有的HotSpot 虚拟机进程

(2) jinfo:Configuration Info for Java,显示虚拟机配置信息

(3)jmap:Memory Map for java,生成虚拟机内存转储快照(heapdump 文件)

(4) jstack:Stack Trace for Java,显示虚拟机的线程快照,定位线程出现长时间停顿的原因(死锁、死循环等)

(5)jstat:JVM Statistics Monitoring Tool,收集 HotSpot VM 各方面运行数据

(6) HSDIS: JIT 生成代码反汇编

1.1 jps

JVM Process Status Tool,显示指定系统内所有的HotSpot 虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称及这些进程的本地虚拟机唯一ID(LVMID,Local Virtual Machine Identifier)。
命令格式为:jps [options] [hostid]
可选参数如下:

  • -q 指定jps只输出进程ID,不输出类的短名称
  • -m 输出传递给java进程(主函数)的参数
  • -l 输出主函数的完整路径
  • -v 显示传递给JVM的参数
C:\Users\admin>jps -l
8896
1252 org.jetbrains.idea.maven.server.RemoteMavenServer
9460 sun.tools.jps.Jps
5324 org.jetbrains.jps.cmdline.Launcher

前面的 ID 为进程 ID,后面为主类的完整路径。
jps 可以通过RMI协议查询开启了 RMI 服务的远程虚拟机进程状态,参数 hostid 即 RMI 注册表中注册的主机名。

1.2 jinfo

jinfo(Configuration Info for Java) 可以实时查看和调整虚拟机各项参数。jps 命令的 -v 参数可以查看虚拟机启动时显式指定的参数列表,但若想知道未显式指定的参数的系统默认值,可以使用 jinfo 的 -flag 选项进行查询。
命令格式为:jinfo [options] pid
可选参数如下:

  • -flag
    打印指定JVM的参数值
  • -flags
    打印JVM的参数
  • -flag [+|-]
    启用/禁用指定的虚拟机参数
  • -flag =
    设置指定的参数值
  • -sysprops
    打印出 Java 系统属性,功能类似 System.getProperties();
C:\Users\admin>jinfo -flag CMSInitiatingOccupancyFraction 5324
-XX:CMSInitiatin
1.3 jmap

jmap(Memory Map for Java) 用于生成堆转储快照(heapdump或dump 文件)。该命令还可以查询 finalize 执行队列、Java 堆和永久代的详细信息,如空间使用率、当前所用的收集器信息等。
命令格式为:jmap [options] vmid
可选参数如下:

  • -dump
    生成 Java 堆转储快照,格式为: -dump:[live, ]format=b, file=, live 参数表示是否只dump存活对象,file 参数值要加上完整路径及导出的文件名称,示例如下:
    jmap -dump:format=b,file=d:\OOMTestHeap.hprof 9028(进程号)
  • -finalizerinfo
    显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。(只在 linux 平台有效)
  • -heap
    显示堆详细信息,如回收器、参数配置、分代信息等(只在 linux 平台有效)
  • -histo
    显示堆中对象统计信息,包括类、实例数量、合计容量
    如果信息过长,命令控制台不能显示靠前的信息,可将信息保存至磁盘,如:
C:\Users\admin>jmap -histo pid > e:111.txt
  • -F
    当使用 -dump 没有响应时,可使用该选项强制生成 dump 快照(只在 linux 平台有效)
1.4 jstack

jstack (Stack Trace for Java) 命令用于生成虚拟机当前时刻的线程快照(threaddump/javacore 文件),线程快照是指当前虚拟机内每条线程正在执行的方法堆栈的集合。

线程快照主要用于定位线程长时间停顿的原因,如线程死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。

命令格式为:jstack [option] vmid
可选参数如下:

  • -F
    当正常输出的请求不被响应时,强制输出线程堆栈
  • -l
    打印关于锁的信息
  • -m
    如果调用到本地方法的话,显示 C/C++ 的堆栈信息

对一个线程死锁的进程 jstack 如下:

C:\Users\admin>jps -l
7604
8168 sun.tools.jps.Jps
8952 org.jetbrains.jps.cmdline.Launcher
9852 com.java.jvm.test.DeadLockTest

jstack 分析进程9852(DeadLockTest)

C:\Users\admin>jstack -l 9852
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.162-b12 mixed mode):

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

   Locked ownable synchronizers:
        - None

"Thread-1" #12 prio=5 os_prio=0 tid=0x0000000018c38000 nid=0x2744 waiting for monitor entry [0x00000000198ef000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.java.jvm.test.DeadLockTest.run(DeadLockTest.java:73)
        - waiting to lock <0x00000000d65a3080> (a java.lang.Object)
        - locked <0x00000000d65a3090> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000018c35000 nid=0x2784 waiting for monitor entry [0x00000000197ef000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.java.jvm.test.DeadLockTest.run(DeadLockTest.java:60)
        - waiting to lock <0x00000000d65a3090> (a java.lang.Object)
        - locked <0x00000000d65a3080> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None
...
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000018b35000 nid=0x2708 runnable [0x00000000191ee000]
   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 <0x00000000d662c178> (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 <0x00000000d662c178> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000017768800 nid=0x26a8 in Object.wait() [0x0000000018aef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6408ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000d6408ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:212)

   Locked ownable synchronizers:
        - None

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

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=2 tid=0x0000000017747000 nid=0x26ac runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002c18800 nid=0x25a8 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002c1a000 nid=0x26fc runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002c1b800 nid=0x2748 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002c1d000 nid=0x2754 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000000018c2d000 nid=0x26e4 waiting on condition

JNI global references: 12


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000002cfb788 (object 0x00000000d65a3080, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000002cfcc28 (object 0x00000000d65a3090, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.java.jvm.test.DeadLockTest.run(DeadLockTest.java:73)
        - waiting to lock <0x00000000d65a3080> (a java.lang.Object)
        - locked <0x00000000d65a3090> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at com.java.jvm.test.DeadLockTest.run(DeadLockTest.java:60)
        - waiting to lock <0x00000000d65a3090> (a java.lang.Object)
        - locked <0x00000000d65a3080> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

从最后打印的信息可以分析出:
Thread-1 在等待由Thread-0持有的对象锁(lock <0x00000000d65a3080> );
Thread-0 在等待由Thread-1持有的对象锁(lock <0x00000000d65a3090> );
从而导致死锁问题。

此外,在JDK 1.5中Thread 类增加了一个 getAllStackTraces()方法用于获取虚拟机中所有线程的 StackTraceElement 对象,可以完成 jstack 大部分功能,在实际项目中可以使用该方法做个管理线程 stack 的页面,方便查看线程堆栈情况。

1.5 jstat

jstat(JVM Statistics-Monitoring Tool) 用于监视虚拟机各种运行状态信息,该命令可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。

命令格式为:jstat [options] vmid [interval [s|ms] [count] ]
注:如果为本地虚拟机进程,VMID 和 LVMID相同,如果为远程虚拟机进程,VMID 的格式为:
[protocol:][//lvmid[@hostname[:port]/servername]

interval 表示查询间隔,count 表示查询次数,默认只查询一次。
如没500ms 查询进程 9852 垃圾收集情况,查10次,则命令如下:

jstat -gc 9852 500 10

option 此处表示虚拟机指定信息,主要包括类装载、垃圾收集、运行期编译状况3类,具体参数如下:

  • -class
    监视类装载、卸载、总空间及类装载所耗费的时间
  • -gc
    监视堆状况,各区、代容量,已用空间,GC等信息
  • -gccapacity
    主要关注堆各区使用到的最大、最小空间
  • -gcutil
    主要关注已用空间占总空间的百分比
  • -gccause
    与gcutil 功能一样,额外输出导致上一次GC产生的原因
  • -gcnew
    监视新生代GC
  • -gcnewcapacity
    主要关注堆使用到的最大、最小空间
  • -gcold
    监视老年代GC
  • -gcoldcapacity
    主要关注堆使用到的最大、最小空间
  • -compiler
    输出 JIT 编译器编译过的方法、耗时信息

输出的各列描述如下表:

显示列名描述
S0C 年轻代中第一个survivor(幸存区)的容量 (KB)
S1C 年轻代中第二个survivor(幸存区)的容量 (KB)
S0U 年轻代中第一个survivor(幸存区)目前已使用空间 (KB)
S1U 年轻代中第二个survivor(幸存区)目前已使用空间 (KB)
EC 年轻代中Eden(伊甸园)的容量 (KB)
EU 年轻代中Eden(伊甸园)目前已使用空间 (KB)
OC Old代的容量 (KB)
OU Old代目前已使用空间 (KB)
PC Perm(持久代)的容量 (KB)
PU Perm(持久代)目前已使用空间 (KB)
YGC 从应用程序启动到采样时年轻代中gc次数
YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)
FGC 从应用程序启动到采样时old代(全gc)gc次数
FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT从应用程序启动到采样时gc用的总时间(s)
S0 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E 年轻代中Eden(伊甸园)已使用的占当前容量百分比
O old代已使用的占当前容量百分比
P perm代已使用的占当前容量百分比
YGC 从应用程序启动到采样时年轻代中gc次数
YGCT 从应用程序启动到采样时年轻代中gc所用时间(s)
FGC 从应用程序启动到采样时old代(全gc)gc次数
FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT从应用程序启动到采样时gc用的总时间(s)
1.6 HSDIS: JIT 生成代码反汇编

分析程序如何执行,可以使用软件调试工具(GDB、Windbg等)来断点调试,但在JVM中会比较麻烦,因为执行代码是通过 JIT 编译器动态生成到 CodeBuffer 中的,而 HSDIS 插件正好可以解决这个问题。

HSDIS 是 HotSpot 虚拟机 JIT 编译代码的反汇编插件,包含在虚拟机源码中。其作用是让虚拟机的 -XX:+PrintAssembly 指令调用它来把动态生成的本地代码还原为汇编代码输出(包括注释),进而分析问题。

注: 如果 HotSpot 为Debug 版的,可以直接通过 -XX:+PrintAssembly 指令使用插件;如果是 Product 版的,需加上 -XX:+UnlockDiagnosticVMOption 参数。

命令格式为:
java -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*类名.方法名 -XX:CompileCommand=compileonly, *类名.方法名 包名.类名

参数说明如下:

  • –XX:+PrintAssembly
    输出反汇编内容
  • -Xcomp
    使虚拟机以编译模式执行代码
  • -XX:CompileCommand
    使虚拟机不内联(don’t inline)方法,并且只编译该(compileonly)方法
  • -其中类名为java文件编译后的class类名

实例如下:

package test;
public class Test {
  int a = 1;
  int b = 2;
  public int sum() {
    return a+b;
  }
  public static void main(String[] args){
    Test test = new Test();
    test.sum();
  }
}

javac编译java文件后,命令窗口进入文件目录,反汇编命令为:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*Test.sum -XX:CompileCommand=compileonly,*Test.sum Test


参考资料:
深入理解Java虚拟机 JVM高级特性与最佳实践

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值