JVM监控及诊断工具-命令行篇
# JVM监控及诊断工具-命令行篇
# 概述
性能诊断是软件工程师在日常工作中需要经常面对和解决的问题,在用户体验至上的今天,解决好应用的性能问题能带来非常大的收益。Java 作为最流行的编程语言之一,其应用性能诊断一直受到业界广泛关注。我们知道可能造成 Java 应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问、网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。
JVM监控&调优体会:
- 使用数据说明问题,使用知识分析问题,使用工具处理问题。
- 无监控、不调优!
简单命令行工具
在我们刚接触Java学习的时候,大家肯定最先了解的两个命令就是javac,java,那么除此之外,还有没有其他的命令可以供我们使用呢?
我们进入到安装JDK的bin目录,发现还有一系列辅助工具。这些辅助工具用来获取目标 JVM 不同方面、不同层次的信息,帮助开发人员很好地解决Java应用程序的一些疑难杂症。附,辅助工具源码地址 (opens new window)
mac系统下JDK 8示例:/Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home/bin

其实JVM的监控工具主要分为命令行和GUI软件(图形用户界面),这里只要讲命令行工具。不要建议死记硬背,在有需要的时候翻阅,多实践。在忘记命令参数的时候可以使用帮助信息,即xxx -h或xxx -help。
# jps:查看正在运行的Java进程
jps(Java Process Status):显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。
# 基本语法
jps基本使用语法为:jps [options] [hostid]。 在不加任何参数的情况下,输出结果为本地虚拟机唯一ID + 主类的名称。可以通过追加参数,来打印额外的信息。更多说明参见官方文档 (opens new window)。
- options:详见下文。
- hostid:详见下文。
# options参数
| 参数选项 | 说明 |
|---|---|
| -q | 仅仅显示LVMID(Local Virtual Machine ID),即本地虚拟机唯一ID,不显示主类的名称等 |
| -l | 输出应用程序主类的全类名或jar完整路径(如果进程执行的是jar包)。 |
| -m | 输出虚拟机进程启动时传递给主类main()的参数 |
| -v | 列出虚拟机进程启动时的JVM参数。比如:-Xms20m -Xmx50m是启动程序指定的JVM参数。 |
以上参数可以综合使用。
提示
如果某 Java 进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该Java 进程。
# hostid参数
RMI注册表中注册的主机名。如果想要远程监控主机上的 Java 程序,需要安装jstatd。
对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具。
# jstat:查看JVM统计信息
jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。更多说明参见官方文档 (opens new window)。
它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。
# 基本语法
jstat基本使用语法为:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]。
- option:详见下文
- vmid:指JVM进程ID,也就是
jps看到前面的号码(本地虚拟机唯一ID),必填。 - interval:用于指定输出统计数据的周期,单位为毫秒。即:查询间隔
- count:用于指定查询的总次数
- -t:可以在输出信息前加上一个Timestamp列,显示程序的运行时间。单位:秒
- -h: 可以在周期性数据输出时,输出多少行数据后输出一个表头信息
# option参数
jstat的option参数大体可以分为三类:类装载相关的、垃圾回收相关的以及JIT相关的。
类装载相关的:
| 参数选项 | 说明 |
|---|---|
| -class | 显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等 |
垃圾回收相关的(后面附上表头说明):
| 参数选项 | 说明 |
|---|---|
| -gc | 显示与GC相关的堆信息。包括Eden区、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息。 |
| -gccapacity | 显示内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间。 |
| -gcutil | 显示内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比。 |
| -gccause | 与-gcutil功能一样,但是会额外输出导致最后一次或当前正在发生的GC产生的原因。 |
| -gcnew | 显示新生代GC状况 |
| -gcnewcapacity | 显示内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间 |
| -geold | 显示老年代GC状况 |
| -gcoldcapacity | 显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间 |
| -gcpermcapacity | 显示永久代使用到的最大、最小空间。 |
附,jstat输出结果主要表头相关说明:
| 表头 | 含义(存储单位字节,时间则为秒) |
|---|---|
| S0C | 幸存者0区的大小 |
| S1C | 幸存者1区的大小 |
| S0U | 幸存者0区已使用的大小 |
| S1U | 幸存者1区已使用的大小 |
| EC | Eden区的大小 |
| EU | Eden区已使用的大小 |
| OC | 老年代的大小 |
| OU | 老年代已使用的大小 |
| MC | 元空间的大小 |
| MU | 元空间已使用的大小 |
| CCSC | 压缩类空间的大小 |
| CCSU | 压缩类空间已使用的大小 |
| YGC | 从应用程序启动到采样时Young GC的次数 |
| YGCT | 从应用程序启动到采样时Young GC消耗时间(秒) |
| FGC | 从应用程序启动到采样时Full GC的次数 |
| FGCT | 从应用程序启动到采样时的Full GC的消耗时间(秒) |
| GCT | 从应用程序启动到采样时GC的总时间 |
JIT相关的:
| 参数选项 | 说明 |
|---|---|
| -compiler | 显示JIT编译器编译过的方法、耗时等信息 |
| -printcompilation | 输出已经被JIT编译的方法 |
jstat 可以用来判断是否出现内存泄漏。
第1步:在长时间运行的 Java 程序中,我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中 OU 列(即已占用的老年代内存)的最小值。
第2步:然后,我们每隔一段较长的时间重复一次上述操作,来获得多组 OU 最小值。如果这些值呈上涨趋势,则说明该 Java 程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。
# jinfo:实时查看和修改JVM配置参数
jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。更多说明参见官方文档 (opens new window)。
在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值。
# 基本语法
jinfo基本使用语法为:jinfo [option] <pid>。
- option:详见下文。
- pid:指JVM进程ID,必填。
# option参数
| 参数选项 | 说明 |
|---|---|
| no option | 输出全部的参数和系统属性 |
| -flag name | 输出对应名称的参数 |
| -flag [+-]name | 开启或者关闭对应名称的参数,只有被标记为manageable的参数才可以被动态修改。 |
| -flag name=value | 设定对应名称的参数 |
| -flags | 输出全部的参数 |
| -sysprops | 输出系统属性 |
注意,jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。但是,并非所有参数都支持动态修改!只有被标记为manageable的参数才可以被动态修改,也就是说这个修改是有限的。如下,是标记为manageable的参数(不同JDK版本,参数可能不同):
wenwl@wenwls-iMac ~ % java -XX:+PrintFlagsFinal -version | grep manageable
intx CMSAbortablePrecleanWaitMillis = 100 {manageable}
intx CMSTriggerInterval = -1 {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 PrintClassHistogramAfterFullGC = false {manageable}
bool PrintClassHistogramBeforeFullGC = false {manageable}
bool PrintConcurrentLocks = false {manageable}
bool PrintGC = false {manageable}
bool PrintGCDateStamps = false {manageable}
bool PrintGCDetails = false {manageable}
bool PrintGCID = false {manageable}
bool PrintGCTimeStamps = false {manageable}
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
拓展
java -XX:+PrintFlagsInitial:查看所有JVM参数启动的初始值。java -XX:+PrintFlagsFinal:查看所有JVM参数的最终值(值前面添加冒号: 的是修改之后的值,没有添加的都是没有发生改变的初始值)。java -参数名称:+PrintCommandLineFlags:查看那些已经被用户或者JVM设置过的详细的XX参数的名称和值
# jmap:导出内存印象文件&内存使用情况
jmap(JVM Memory Map):作用一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。更详细的说明参见官方文档 (opens new window)
# 基本语法
jmap的基本语法如下:
jmap [option] <pid>:option详细见下文,jmap [option] <executable <core>:<executable <core>代表可执行的代码,比如使用> 文件名称来指定生成的dump文件的生成位置。jmap [option] [server_id@] <remote server IP or hostname>:[server_id@]<……>是为远程连接准备的。
# option参数
| 参数选项 | 说明 |
|---|---|
| -dump | 生成dump文件(Java堆转储快照)。特别的,-dump:live只保存堆中的存活对象 |
| -heap | 输出整个堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等 |
| -histo | 输出堆空间中对象的统计信息,包括类、实例数量和合计容量。特别的,-histo:live只统计堆中的存活对象 |
| -finalizerinfo | 显示在F-Queue中等待Finalizer线程执行finalize方法的对象,仅linux/solaris平台有效 |
| -permstat | 以ClassLoader为统计口径输出永久代的内存状态信息,仅linux/solaris平台有效 |
| -F | 当虚拟机进程对-dump选项没有任何响应时,强制执行生成dump文件,仅linux/solaris平台有效 |
| -J <flag> | 传递参数给jmap启动的jvm |
这些参数和linux下输入显示的命令多少会有不同,包括也受JDK版本的影响。
# 使用1:导出内存映像文件
一般来说,使用jmap指令生成dump文件的操作算是最常用的jmap命令之一,即将堆中所有存活对象导出一个文件之中。
Heap Dump又叫堆存储文件,指一个Java进程在某个时间点的内存快照。Heap Dump在出发内存快照的时候会保存此刻的信息,具体如下:
- All Objects: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.
导出内存映像文件,分为手动方式以及自动方式,在介绍之前注意一下事项:
- 通常在写Heap Dump文件前会触发一次Full GC,所以Heap Dump文件保存的都是Full GC留下的对象信息。(自动方式才会这样做,而手动不会在Full GC之后生成Dump)。
- 由于生成dump文件比较耗时,因此大家需要耐心等待,尤其是大内存镜像生成dump文件则需要耗费更长时间完成。。
# 手动方式
jmap -dump:format=b,file=<filename.hprof> <pid>jmap -dump:live,format=b,file=<filename.hprof> <pid>
说明:
<filename.hprof>中的filename是导出文件名称,而.hprof是后缀名,该值可以省略。<pid>是进程ID。format=b表示生成的是标准的dump文件,用来进行格式限定-dump:live指只dump出存活的对象。
# 自动方式
当程序发生OOM退出系统,一些瞬时信息都随着程序终止而消失,而重现OOM问题往往比较困难或耗时,若此时能够在OOM时刻,自动导出dump文件就显得非常迫切。这里介绍一种比较常用的取得堆快照文档的方法:
-XX:+HeapDumpOnOutOfMemoryError:在程序OOM时,导出应用程序堆快照。-XX:HeapDumpPath=<filename.hprof>:指定堆快照保存位置
# 使用2:显示堆内存相关信息
jmap -heap <pid>: 只是时间点上的堆信息,而jstat后面可以添加参数,可以指定时间动态观察数据改变情况,而图形化界面工具 ,例如JVisualVM等,它们可以用图表的方式动态展示出相关信息,更加直观明了。jmap -histo <pid>: 输出堆中对象的同级信息,包括类、实例数量和合计容量,也是这一时刻的内存中的对象信息。
# 总结
注意,由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由jmap导出的堆快照必定是安全点位置的,这可能导致基于该堆快照的分析结果存在偏差。
举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:live选项将无法探知到这些对象。另外,如果某个线程长时间无法跑到安全点,jmap将一直等下去。与前面讲的jstat则不同,垃圾回收器会主动将jstat所需要的摘要数据保存至固定位置之中,而jstat只需直接读取即可。
# jhat:JDK自导堆分析工具
jhat(JVM Heap Analysis Tool):Sun JDK提供的jhat命令与jmap命令搭配使用,用于分析jmap生成的heap dump文件(堆转储快照)。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。更多详见官方文档 (opens new window)
使用了jhat命令,就启动了一个http服务,端口是7000,即http://localhost:7000/,就可以在浏览器里分析。
说明:jhat命令在JDK9、JDK10中已经被删除,官方建议用VisualVM代替,所以该指令只需简单了解一下即可。
# 基本语法
jhat基本语法:jhat <option> <dumpfile>。
- option:见下文
- dumpfile:要解析的dump文件
# option参数
| 参数选项 | 说明 |
|---|---|
| -stack false|true | 关闭|打开对象分配调用栈跟踪 |
| -refs false|true | 关闭|打开对象引用跟踪 |
| -port port-number | 设置jhat HTTP Server的端口号,默认7000 |
| -exclude exclude-file | 执行对象查询时需要排除的数据成员 |
| -baseline exclude-file | 指定一个基准堆转储 |
| -debug int | 设置debug级别 |
| -version | 启动后显示版本信息就退出 |
| -J <flag> | 传入启动参数,比如-J-Xmx512m |
# jstack:打印JVM线程快照
jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。更多详见官方帮助文档 (opens new window)。
生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。
在Thread Dump中,要留意下面几种状态:
- 死锁,Deadlock(重点关注)
- 等待资源,Waiting on condition(重点关注)
- 等待获取监视器,Waiting on monitor entry(重点关注)
- 阻塞,Blocked(重点关注)
- 执行中,Runnable
- 暂停,Suspended
- 对象等待中,Object.wait() 或 TIMED_WAITING
- 停止,Parked
# 基本语法
jstack [-l] <pid>:连接到正在运行的进程jstack -F [-m] [-l] <pid>:连接到挂起的进程jstack [-m] [-l] <executable> <core>:连接到核心文件jstack [-m] [-l] [server_id@]<remote server IP or hostname>:连接到远程调试服务器,远程服务器需要配置RMI。
# option参数
| 参数选项 | 说明 |
|---|---|
| -F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
| -l | 除堆栈外,显示关于锁的附加信息 |
| -m | 如果调用本地方法的话,可以显示C/C++的堆栈 |
# jcmd:多功能命令行
在JDK 1.7以后,新增了一个命令行工具jcmd。它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。更多详见官方帮助文档 (opens new window)。
jcmd拥有jmap的大部分功能,并且在Oracle的官方网站上也推荐使用jcmd命令代jmap命令。
# 基本语法
jcmd -l: 列出所有的JVM进程。jcmd <pid> hlep: 针对指定的进程,列出支持的所有具体命令jcmd <pid | main class> <command ...|PerfCounter.print|-f file>: 针对指定的进程,使用某个具体命令
例如某个进程支持的所有具体命令:
wenwl@wenwls-iMac ~ % jcmd 64886 help
64886:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
VM.classloader_stats
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.finalizer_info
GC.heap_info
GC.run_finalization
GC.run
VM.uptime
VM.dynlibs
VM.flags
VM.system_properties
VM.command_line
VM.version
help
For more information about a specific command use 'help <command>'.
这些指令通常是可以替换上文提到的指令操作:
Thread.print可以替换jstack指令GC.class_histogram可以替换jmap中的-histo操作GC.heap_dump可以替换jmap中的-dump操作GC.run可以查看GC的执行情况VM.uptime可以查看程序的总执行时间,可以替换jstat指令中的-t操作VM.system_properties可以替换jinfo -sysprops <pid>VM.flags可以获取JVM的配置参数信息
# jstatd:远程主机信息收集
之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、jstat)。为了启用远程监控,则需要配合使用jstatd 工具。命令jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。
