JVM性能监控与故障处理工具 - Sanarous的博客

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

Java 与 C++ 之间有一堵由内存分配和垃圾收集技术所围成的“高墙”,墙里面的人想出去,墙外面的人想进来。

概述

在给一个系统定位问题的时候,知识、经验是关键基础,而工具是运用知识去处理数据的手段,这里的数据在 JVM 中包括:运行日志、异常堆栈、GC日志、线程快照(threaddump/javacore文件)、堆转储快照(headdump/hprof文件)等。经常使用适当的虚拟机监控和分析的工具可以加快我们分析数据、定位解决问题的速度。但是下面介绍的所有工具,都只是对知识技能的一层“封装”,吃透底层原理才是最重要的,工具的使用是次要的。

JDK命令行工具

一般来说,Java 开发人员在初学 Java 的时候就知道有java.exejavac.exe两个命令行工具,这是最开始我们写Hello World的时候用到的,但是却并不是所有 Java 程序员都知道除了这两个命令行工具外,JDK 自带的bin目录下的其它很多exe工具的数量和功能是在不断的增加和提升的。

由于我个人使用的是JDK1.8版本,所以我们以windows下JDK1.8版本下bin目录工具为例,如果是linux或者macOS可能看到的不太一样。

其中细心的人可以发现好像大多数工具都是 17KB,嘿,难道是 JDK 开发团队在刻意跟我们炫耀高超编程技巧?其实当然不是,这只是因为上面的 exe 命令行工具大多数都是jdk/lib/tools.jar工具的一层很薄的封装而已,其中主要的功能实现都是在tools类库中实现的。

那么为啥要采用 java 代码来实现这些监控工具呢?

其实啊,主要是因为实际应用程序部署到生产环境后,无论是直接接触物理服务器还是远程 Telnet 到服务器上都可能受到限制,而借助tools.jar类库里面的工具,我们就可以直接在应用程序中实现强大的监控分析工具。

如果使用的是 JDK1.5 及之前版本的 JDK 的话,在程序启动的时候需要添加参数-Dcom.sun.management.jmxremote来开启 JMX 管理功能,因为其中监控工具大多数都是基于 JMX 的,而如果是 JDK1.6 之后的版本,那么 JMX 管理是默认开启的,即不需要手动添加参数。

常用的JDK监控和故障处理工具

名称主要作用
jps(JVM Process Status Tool)显示指定系统内所有的HotSpot虚拟机进程
jstat(JVM Statistics Monitoring Tool)用于收集HotSpot虚拟机各方面的运行数据
jinfo(Configuration Info for Java)显示虚拟机配置信息
jmap(Memory Map for Java)生成虚拟机的内存转储快照(heapdump文件)
jhat(JVM Heap Dump Browser)用于分析heapdump文件,会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果
jstack(Stack Trace for Java)显示虚拟机的线程快照

jps:虚拟机进程状况工具

ps 取名其实和 Unix 的 ps 命令一样,也就是用来列出正在运行的虚拟机进程,并显示虚拟机执行主类,其实在 windows 下或者 linux 下 ps 命令也可以查询到虚拟机进程的id,但是如果同时启动了多个虚拟机进程,且无法通过进程名称定位时,那就只能依赖 jps 命令显示主类来查询虚拟机进程了。

jps 命令格式:

1
jps [options] [hostid]

用法比较简单,比如我在本地启动了两个虚拟机进程,主类名称分别是cn.bestzuo.MultiThreadcn.bestzuo.SynAddRunnable,在使用jps -l命令查看进程:

1
2
3
4
5
6
C:\Program Files\Java\jdk1.8.0_191\bin> jps -l
+ 12256 cn.bestzuo.SynAddRunnable
11012 org.jetbrains.jps.cmdline.Launcher
+ 4696 cn.bestzuo.MultiThread
5480 sun.tools.jps.Jps
8408

上面绿色的两个进程就是虚拟机启动的进程,前面的数字表示进程 id。

那么后面的-l是选择使用的参数,jps 命令可以使用的参数如下:

选项作用
-q只输出 LVMID ,省略主类的名称
-m输出虚拟机进程启动时传递给主类 main() 函数的参数
-l输出主类的全名,如果进程执行的是 jar 包,输出 Jar 路径
-v输出虚拟机进程启动时 JVM 参数

jstat:虚拟机统计信息监视工具

jstat 用于监视虚拟机各种运行状态信息的命令行工具,它可以显示本地或者远程虚拟机中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。

jstat 命令格式为:

1
jstat [option vmid [interval[s|ms] [count]] ]

对于命令格式中的 VMID 和 LVMID 需要特别说明一下,如果是本地虚拟机进程,那么 VMID 和 LVMID 是一致的,如果是远程虚拟机进程,那么 VMID 的格式应该是:

1
[protocol:][//]lvmid[@hostname[:port]/servername]

其中参数 interval 和 count 代表查询间隔和次数,如果省略这两个参数,说明只查询一次。

假设需要每 250 毫秒查询一次进程 id 为 2764 垃圾收集情况,一共查询 20 次,那么命令应该是:

1
jstat -gc 2764 250 20

选项 option 代表用户想要查询的虚拟机信息,主要分三类:类装载、垃圾收集和运行期编译情况。

选项作用
-class监视类装载、卸载数量、总空间以及类装载所耗费的时间
-gc监视 java 堆状况,包括 Eden 区、两个 Survivor 区、老年代、永久代容量、已用空间、GC时间合计等信息
-gccapacity监视内容与 -gc 基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间
-gcutil监视内容与 -gc 基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause与 -gcutil 工具一样,但是会额外输出导致上一次 GC 产生的原因
-gcnew监视新生代 GC 状况
-gcnewcapacity监视内容与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间
-gcold监视老年代 GC 状况
-gcoldcapacity监视内容与 -gcold 基本相同,输出主要关注使用到的最大、最小空间
-gcpermcapacity输出永久代使用到的最大、最小空间
-compiler输出 JIT 编译器编译过的方法、耗时等信息
-printcompilation输出已经被 JIT 编译过的方法

由于 jstat 监视选项太多,因此只测试一下其中一项观察结果,依然测试主类cn.bestzuo.SyncAddRunnable主类,我们可以看到如下结果:

1
2
3
C:\Program Files\Java\jdk1.8.0_191\bin> jstat -gcutil 12256
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 15.47 58.71 0.01 66.07 68.03 1 0.001 0 0.000 0.001

其中:

  • S0 、 S1 表示两个 Survivor 区,其中 S1 区使用了 15.47% 的空间
  • E 代表 Eden 区,即新生代使用了 58.71% 的空间
  • O 代表老年代,老年代中使用了 0.01% 的空间
  • M 表示元数据区,即 metaspace ,使用了 66.07% 的空间
  • CCS 表示压缩使用比例
  • YGC 即 Minor GC,表示发生过一次
  • YGCT 表示新生代 GC 耗时时间
  • FGC 表示 Full GC
  • FGCT 表示 Full GC 耗时时间
  • GCT 表示所有 GC 的总耗时

虽然 jstat 命令行工具不如后面提到的 VisualVM 直观,但是很多服务器管理员在没有 GUI 图形界面时,依然会使用该命令行工具来进行监视。

jinfo:Java配置信息工具

jinfo 的作用是实时地查看和调整虚拟机各项参数。使用 jps 的 -v 参数可以查看虚拟机启动的时候显式指定的参数列表,但是如果想知道未被显示指定的参数的系统默认值,除了找资料外,就只能使用 jinfo 的 -flag 选项进行查询了。

jinfo 的命令格式:

1
jinfo [option] pid

比如我们查询CMSInitialingOccupancyFraction参数值:

1
2
C:\Program Files\Java\jdk1.8.0_191\bin> jinfo -flag CMSInitiatingOccupancyFraction 12256
-XX:CMSInitiatingOccupancyFraction=-1

jmap:Java内存映像工具

jmap 命令用于生成堆转储快照(一般称为 headdump 或 dump 文件)。如果不适用 jmap 命令,要想获取 Java 堆转储快照,也可以采取其它一些比较”暴力“的手段,比如使用-XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在 OOM 异常出现之后自动生成 dump 文件,通过-XX:+HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break]键让虚拟机生成dump文件,又或者在 Linux 系统下通过Kill -3命令发送进程退出信号”吓唬“一下虚拟机,也能拿到 dump 文件。

当然,jmap 的作用并不仅仅是拿到 dump 文件,它还可以查询finalize执行队列、Java 堆和永久代的详细信息,如空间使用率、当前用的哪种收集器等。

和 jinfo 一样,jmap 不少功能在 windows 平台下都是受限的,除了生成 dump 文件的 -dump 选项和用于查看每个类的实例、空间占用统计的 -histo 选项在所有操作系统都提供之外,其余选项只能在 Linux/Solaris 下使用。

jmap 命令格式:

1
jmap [option] vmid

其中 option 选项的合法值和具体含义如下:

选项作用
-dump生成Java堆转储快照
-finliazeinfo显示在F-Queue中等待的Finalizer线程执行finalize方法的对象,只在Linux/Solaris平台下生效
-heap显示Java堆详细信息,如使用哪种垃圾收集器、参数配置、分代状况等,只在Linux/Solaris下有效
-histo显示堆中对象统计信息,包括类、实例数量、合计容量
-permstat以ClassLoader为统计口径显示永久代内存状态,Linux/Solaris下有效
-F当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照,Linux/Solris下有效

比如如下测试:

1
2
3
C:\Users\IcyFenix\jmap -dump:format=b,file=eclipse.bin 3500
Dumping heap to C:\Users\IcyFenix\eclipse.bin ...
Heap dump file created

jhat:虚拟机堆转储快照分析工具

jhat 命令主要与 jmap 命令搭配使用,可以分析 jmap 生成的堆转储快照。 jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 文件的分析结果后可以在浏览器中查看。

不过实际使用中,除非真的没有其它工具可以使用,否则一般不会直接使用 jhat 命令来分析 dump 文件,原因一是一般不会在部署应用程序的服务器上直接分析 dump 文件,即使可以这样做,也会尽量将 dump 文件复制到其它机器上进行分析,因为分析工作本身就是一个耗时并且消耗硬件资源的过程,既然能到其它机器上分析,那就没必要受到命令行工具的限制了;另外一个原因是 jhat 的分析功能较为简陋,其它更多的工具能分析出来的东西更多。

jstack:Java堆栈跟踪工具

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

jstack 的命令格式:

1
jstack [option] vmid

jstack 工具主要选项:

选项作用
-F当正常输出的请求不被响应时,强制输出线程堆栈
-l除堆栈外,显示关于锁的附加信息
-m如果调用到本地方法的话,可以显示C/C++的堆栈

比如我们运行一段死锁的程序,然后查看其锁的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
PS C:\Program Files\Java\jdk1.8.0_191\bin> jstack -l 1328
2019-05-14 20:45:30
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):

省略堆栈信息
...

"VM Thread" os_prio=2 tid=0x0000000017f68000 nid=0x18c0 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000030e9000 nid=0x9e8 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000030ea800 nid=0x223c runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000030ec000 nid=0x16f4 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000030ee800 nid=0x3af4 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00000000030f1000 nid=0x1f94 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00000000030f2000 nid=0x1440 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000000019ade000 nid=0x392c waiting on condition

JNI global references: 12


Found one Java-level deadlock:
=============================
"Thread-199":
waiting to lock monitor 0x0000000019b95918 (object 0x00000000d5b7d5f0, a java.lang.Integer),
which is held by "Thread-47"
"Thread-47":
waiting to lock monitor 0x00000000031cd708 (object 0x00000000d5b7d5e0, a java.lang.Integer),
which is held by "Thread-58"
"Thread-58":
waiting to lock monitor 0x0000000019b95918 (object 0x00000000d5b7d5f0, a java.lang.Integer),
which is held by "Thread-47"

Java stack information for the threads listed above:
===================================================
"Thread-199":
at cn.bestzuo.SynAddRunnable.run(SynAddRunnable.java:13)
- waiting to lock <0x00000000d5b7d5f0> (a java.lang.Integer)
at java.lang.Thread.run(Thread.java:748)
"Thread-47":
at cn.bestzuo.SynAddRunnable.run(SynAddRunnable.java:14)
- waiting to lock <0x00000000d5b7d5e0> (a java.lang.Integer)
- locked <0x00000000d5b7d5f0> (a java.lang.Integer)
at java.lang.Thread.run(Thread.java:748)
"Thread-58":
at cn.bestzuo.SynAddRunnable.run(SynAddRunnable.java:14)
- waiting to lock <0x00000000d5b7d5f0> (a java.lang.Integer)
- locked <0x00000000d5b7d5e0> (a java.lang.Integer)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.
注意:在 JDK1.5 中,java.lang.Thread类新增了一个getAllStackTraces()方法用于获取虚拟机中所有线程的StackTraceElement对象。使用这个方法可以通过简单的几行代码就完成jstack的大部分功能,在实际项目中不妨调用这个方法做一个管理员页面,可以随时使用浏览器来查看线程堆栈。

JDK可视化工具

JDK 中除了提供大量的命令行工具外,还有两个功能非常强大的可视化工具:JConsoleVisualVM,这两个是 JDK 的正式成员。

其中JConsole是 JDK1.5 时期就已经提供的虚拟机监控工具,而VisualVM在 JDK1.6 Update7 中才首次发布,现在已经成为 Sun(Oracle)主力推动的多合一故障处理工具,并且已经从 JDK 中分离出来成为可以独立发展的开源项目。

上面两者由于是可视化工具,所以这里就不再赘述,内置功能相当于把前面提到的 JDK 命令行工具实现可视化。

JVM-Monitor监控工具

还未完全开发完成,但是已经可以使用,是一个浏览器在线的 JVM 监控页面。

jvm-monitor的Github地址

如果这篇文章对您很有帮助,不妨
-------------    本文结束  感谢您的阅读    -------------
0%