JVM性能调优【二】------ 工具篇
文章目录
- [JVM性能调优【二】------ 工具篇](#JVM性能调优【二】—— 工具篇)
-
- [2-1 JVM性能调优工具概述](#2-1 JVM性能调优工具概述)
- [2-2 内置监控工具-jps【非常常用】](#2-2 内置监控工具-jps【非常常用】)
- [2-3 内置监控工具-jstat【非常常用】](#2-3 内置监控工具-jstat【非常常用】)
- [2-3 内置故障排查工具-jinfo](#2-3 内置故障排查工具-jinfo)
- [2-4 内置故障排查工具-jmap](#2-4 内置故障排查工具-jmap)
- [2-5 内置故障排查工具-jstack](#2-5 内置故障排查工具-jstack)
- [2-6 内置故障排查工具-jhat](#2-6 内置故障排查工具-jhat)
- [2-7 内置故障排查工具-jcmd](#2-7 内置故障排查工具-jcmd)
-
- jcmd
-
- 作用
- 使用说明
- 参数说明
- 使用示例
- 支持的命令
-
- [1 help [options] [arguments]](#1 help [options] [arguments])
- [2 Compiler.codecache](#2 Compiler.codecache)
- [3 Compiler.codelist](#3 Compiler.codelist)
- [4 Compiler.queue](#4 Compiler.queue)
- [5 Compiler.directives_add filename arguments](#5 Compiler.directives_add filename arguments)
- [6 Compiler.directives_clear](#6 Compiler.directives_clear)
- [7 Compiler.directives_print](#7 Compiler.directives_print)
- [8 Compiler.directives_remove](#8 Compiler.directives_remove)
- [9 GC.class_histogram [options]](#9 GC.class_histogram [options])
- [10 GC.class_stats [options] [arguments]](#10 GC.class_stats [options] [arguments])
- [11 GC.finalizer_info](#11 GC.finalizer_info)
- [12 GC.heap_dump [options] [arguments]](#12 GC.heap_dump [options] [arguments])
- [13 GC.heap_info](#13 GC.heap_info)
- [14 GC.run](#14 GC.run)
- [15 GC.run_finalization](#15 GC.run_finalization)
- [16 JFR.check [options]](#16 JFR.check [options])
- [17 JFR.configure [options]](#17 JFR.configure [options])
- [18 JFR.dump [options]](#18 JFR.dump [options])
- [19 JFR.start [options]](#19 JFR.start [options])
- [20 JFR.stop [options]](#20 JFR.stop [options])
- [21 JVMTI.agent_load [arguments]](#21 JVMTI.agent_load [arguments])
- [22 JVMTI.data_dump](#22 JVMTI.data_dump)
- [23 ManagementAgent.start [options]](#23 ManagementAgent.start [options])
- [24 ManagementAgent.start_local](#24 ManagementAgent.start_local)
- [25 ManagementAgent.status](#25 ManagementAgent.status)
- [26 ManagementAgent.stop](#26 ManagementAgent.stop)
- [27 Thread.print [options]](#27 Thread.print [options])
- [28 VM.check_commercial_features](#28 VM.check_commercial_features)
- [29 VM.unlock_commercial_features](#29 VM.unlock_commercial_features)
- [30 VM.classloader_stats](#30 VM.classloader_stats)
- [31 VM.class_hierarchy [options] [arguments]](#31 VM.class_hierarchy [options] [arguments])
- [32 VM.command_line](#32 VM.command_line)
- [33 VM.dynlibs](#33 VM.dynlibs)
- [34 [VM.info](http://vm.info/)](#34 VM.info)
- [35 VM.log [options]](#35 VM.log [options])
- [36 VM.flags [options]](#36 VM.flags [options])
- [37 VM.native_memory [options]](#37 VM.native_memory [options])
- [38 VM.print_touched_methods](#38 VM.print_touched_methods)
- [39 VM.set_flag [arguments]](#39 VM.set_flag [arguments])
- [40 VM.stringtable [options]](#40 VM.stringtable [options])
- [41 VM.symboltable [options]](#41 VM.symboltable [options])
- [42 VM.systemdictionary](#42 VM.systemdictionary)
- [43 VM.system_properties](#43 VM.system_properties)
- [44 VM.uptime [options]](#44 VM.uptime [options])
- [45 VM.version](#45 VM.version)
- [2-8 内置故障排查工具-jhsdb](#2-8 内置故障排查工具-jhsdb)
-
- jhsdb
-
- 使用说明
- options说明
-
- [jhsdb clhsdb子命令](#jhsdb clhsdb子命令)
- [jhsdb hsdb子命令](#jhsdb hsdb子命令)
- [jhsdb jinfo子命令](#jhsdb jinfo子命令)
- [jhsdb jmap子命令](#jhsdb jmap子命令)
- [jhsdb jstack子命令](#jhsdb jstack子命令)
- [jhsdb jsnap子命令](#jhsdb jsnap子命令)
- [jhsdb debugd子命令](#jhsdb debugd子命令)
- jhsdb和其他工具的对比
- macOS下遇到的问题
- 参考文档
- [2-9 内置可视化工具-jhsdb hsdb](#2-9 内置可视化工具-jhsdb hsdb)
-
- [jhsdb hsdb](#jhsdb hsdb)
- [Java Threads](#Java Threads)
-
- [Java Threads](#Java Threads)
- [Inspect Thread](#Inspect Thread)
- [Stack Memory](#Stack Memory)
- [Show Java stack trace](#Show Java stack trace)
- [Show Thread Information](#Show Thread Information)
- [Find Crashes](#Find Crashes)
- [Windows Console](#Windows Console)
- Tools
- 收集的HSDB相关的文章
- 实验
- 总结
- [2-10 内置可视化工具-jconsole](#2-10 内置可视化工具-jconsole)
- [2-11 内置可视化工具-VisualVM](#2-11 内置可视化工具-VisualVM)
- [2-12 内置可视化工具-JDK Mission Control](#2-12 内置可视化工具-JDK Mission Control)
- [2-13 第三方工具-Memory Analyzer (MAT)](#2-13 第三方工具-Memory Analyzer (MAT))
- [2-14 第三方工具- JITWatch](#2-14 第三方工具- JITWatch)
-
- JITWatch
-
- 安装HSDIS
- [macOS JDK 11下编辑-源码安装HSDIS全过程](#macOS JDK 11下编辑-源码安装HSDIS全过程)
- 启动应用
- 使用JITWatch可视化阅读日志
- [2-15 基于jstatd实现远程连接](#2-15 基于jstatd实现远程连接)
-
- 基于jstatd实现远程连接
-
- 一、配置安全策略
-
- [JDK 8及更低版本](#JDK 8及更低版本)
- [JDK 9及更高版本](#JDK 9及更高版本)
- 二、启动jstatd
- 三、防火墙设置
- 四、本地机器配置连接远程服务器
- 参考文档
- [2-16 基于JMX实现远程连接](#2-16 基于JMX实现远程连接)
- [2-17 基于SSH实现远程连接](#2-17 基于SSH实现远程连接)
2-1 JVM性能调优工具概述
序言
本系列文章旨在总结Java世界中常用的监控工具与故障排查工具,从而提高监控JVM/排查JVM相关问题的效率。如有疑问或需勘误,欢迎留言评论!!!
TIPS
本系列文章基于JDK 11编写 。但总的来说,JDK 11和JDK 8的差异并不大,绝大多数对JDK 8也可适用。当遇到不适用的命令时,可前往对应命令中的JDK 8的地址查看相关文档即可。
JDK 11下载地址如下:
官方下载地址:https://www.oracle.com/java/technologies/javase-jdk11-downloads.html
百度盘加速下载:
shell链接:https://pan.baidu.com/s/1HXjzNDpzin6fXGrZPyQeWQ 密码:aon2如果你同时安装了多个版本的JDK,可参考如下文章实现多个JDK版本共存:
macOS:https://blog.csdn.net/weixin_30532987/article/details/97463829
Windows:https://blog.csdn.net/FCY12345678/article/details/79563524
为什么是基于jdk8讲解的,而在整理这些工具时要基于jdk11?
1、jdk11的工具比jdk8要多一些,比如:jhsdb这个工具从jdk9才开始引入
2、mac版本的jdk8,这篇文章中有些工具无法使用,比如:jinfo这个工具在mac版本的jdk8无法使用,其它操作系统使用无问题
3、哪怕这篇文章是基于jdk11进行讲解的,而实际项目中使用的是jdk8或者其它版本的jdk,也可以参考这篇文章,存在差异的地方都标注出来了
涵盖工具
JDK内置工具
内置工具包括了JDK中提供的常用监控工具以及故障排查工具,主要包括了:
监控工具,包括:
- jps
- jstat
故障排查工具,包括:
- jinfo
- jmap
- jstack
- jcmd
- jhat
- jhsdb
可视化工具,包括:
- jhsdb
- jconsole
- VisualVM
- JDK Mission Control
这些工具从可用性以及授权的不同,主要可以分为三类:
- 正式支持工具:表示这类工具会有长期的技术支持,不同的平台、不同的JDK版本之间,这些工具可能会有一定差异,但总体来说还是比较兼容的。
- 实验性工具 :这类工具会被声明是实验性质,不会有技术支持,一些工具甚至可能会在某个新的JDK版本中突然就消失了。不过这些命令其实也都非常稳定,而且功能很强大,也是可以用在生产的。在实际项目中定位问题发挥的作用也非常的大,所以千万不要一看某个工具是实验性的就不学了。
- 商业授权工具:指的主要是JMC以及JMC需要用到的JFR,这些工具在商业环境中使用的话是要付费的,但一般来说在个人开发环境中使用是免费的。
第三方工具
- Memory Analyzer Tool
- JITWatch
当然除本系列列出的工具外,还有很多其他的工具,比如JProfiler等,但由于其收费的特性,故而本系列不去探讨。感兴趣的可以自行研究下,使用起来也都不难。
2-2 内置监控工具-jps【非常常用】
jps
作用
jps全称Java Virtual Machine Process Status Tool,用来查看JVM进程状态。
TIPS
此命令是实验性的,不受支持。
参考文档:
使用说明
命令如下:
shell
➜ jps -h
usage: jps [--help]
jps [-q] [-mlvV] [<hostid>]
Definitions:
<hostid>: <hostname>[:<port>]
-? -h --help -help: Print this help message and exit.
参数如下:
shell
-q 只显示进程号
-m 显示传递给main方法的参数
-l 显示应用main class的完整包名应用的jar文件完整路径名
-v 显示传递给JVM的参数
-V 禁止输出类名、JAR文件名和传递给main方法的参数,仅显示本地JVM标识符的列表。
## 配合使用
-ml 既可以显示传递给main方法的参数,又可以显示启动类的完整包名
在类UNIX系统机器上有一个和jps类似的工具
shell
ps -ef|grep java 也可以查看java进程,但是jps使用起来明显比ps -ef|grep java方便些,而且展示的结果可控性要高些
此外,还可以使用jps监控远程服务器上的进程
但是要想查看远程服务器上的进程,需要一系列的配置,比较复杂,后续专门探讨有关如何连接远程服务器!
hostid:想要查看的主机的标识符,格式为:[protocol:][[//]hostname][:port][/servername] ,其中:
- protocol:通信协议,默认rmi
- hostname:目标主机的主机名或IP地址
- port:通信端口,对于默认
rmi协议,该参数用来指定rmiregistry远程主机上的端口号。如省略该参数,并且该protocol指示rmi,则使用默认使用1099端口。 - servicename:服务名称,取值取决于实现方式,对于rmi协议,此参数代表远程主机上RMI远程对象的名称
使用示例
shell
jps
jps -m
jps -ml
jps -mlv
# 查看remote.domain这台服务器中JVM进程的信息,使用rmi协议,端口1099
jps -l remote.domain
# 查看remote.domain这台服务器中JVM进程的信息,使用rmi协议,端口1231
jps -l rmi://remote.comain:1231
2-3 内置监控工具-jstat【非常常用】
jstat
作用
jstat全称JVM Statistics Monitoring Tool,用于监控JVM的各种运行状态。
TIPS
此命令是实验性的,不受支持。
参考文档:
使用说明
命令如下:
shell
➜ jstat -h
Usage: jstat --help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
Definitions:
<option> 指定参数,取值可用jstat -options查看
<vmid> VM标识,进程的唯一标识
<lvmid>:如果lvmid是本地VM,那么用进程号即可;
如果是远程进程,格式为<lvmid>[@<hostname>[:<port>]]
<hostname>:目标JVM的主机名;
<port>:目标主机的rmiregistry端口;
-t 用来展示每次采样花费的时间
-h<lines> 每抽样几次就列一个标题,默认0,显示数据第一行的列标题
<interval> 抽样的周期,格式使用:<n>["ms"|"s"],n是数字,ms/s是时间单位,默认是ms
<count> 采样多少次停止
-J<flag> 将<flag>传给运行时系统,例如:-J-Xms48m
-? -h --help Prints this help message.
-help Prints this help message.
option取值如下:
- class:显示类加载器的统计信息
- compiler:显示有关Java HotSpot VM即时编译器行为的统计信息
- gc:显示有关垃圾收集堆行为的统计信息
- gccapacity:统计各个分代(新生代,老年代,持久代)的容量情况
- gccause:显示引起垃圾收集事件的原因
- gcnew:显示有关新生代行为的统计信息
- gcnewcapacity:显示新生代容量
- gcold:显示老年代、元空间区的统计信息
- gcoldcapacity:显示老年代的容量
- gcmetacapacity:显示元空间的容量
- gcutil:显示有关垃圾收集统计信息的摘要
- printcompilation:显示Java HotSpot VM编译方法统计信息
ps :gc相关开头的参数,在定位垃圾收集问题时,是特别有帮助的!
输出信息
-
class:
- Loaded:当前加载的类的数量
- Bytes:当前加载的空间(单位KB)
- Unloaded:卸载的类的数量Number of classes unloaded.
- Bytes:当前卸载的空间(单位KB)
- Time:执行类加载/卸载操作所花费的时间
-
compiler:
- Compiled:执行了多少次编译任务
- Failed:多少次编译任务执行失败
- Invalid:无效的编译任务数
- Time:执行编译任务所花费的时间
- FailedType:上次失败的编译的编译类型
- FailedMethod:上次失败的编译的类名和方法
-
gc:
- S0C:第一个存活区(S0)的容量(KB)
- S1C:第二个存活区(S1)的容量(KB)
- S0U:第一个存活区(S0)使用的大小(KB)
- S1U:第二个存活区(S1)使用的大小(KB)
- EC:伊甸园空间容量(KB)
- EU:伊甸园使用的大小(KB)
- OC:老年代容量(KB)
- OU:老年代使用的大小(KB)
- MC:元空间的大小(KB)
- MU:元空间使用的大小(KB)
- CCSC:压缩的类空间大小(KB)
- CCSU:压缩类空间使用的大小(KB)
- YGC:年轻代垃圾收集事件的数量
- YGCT:年轻代垃圾回收时间
- FGC:Full GC事件的数量
- FGCT:Full GC回收时间
- GCT:垃圾收集总时间
-
gccapacity:
- NGCMN:最小新生代容量(KB)
- NGCMX:最大新生代容量(KB)
- NGC:当前的新生代容量(KB)
- S0C:第一个存活区(S0)的当前容量(KB)
- S1C:第二个存活区(S1)的当前容量(KB)
- EC:当前伊甸园容量(KB)
- OGCMN:最小老年代容量(KB)
- OGCMX:最大老年代容量(KB)
- OGC:当前老年代容量(KB)
- OC:当前old space容量(KB)
- MCMN:最小元空间容量(KB)
- MCMX:最大元空间容量(KB)
- MC:当前元空间的容量(KB)
- CCSMN:压缩的类空间最小容量(KB)
- CCSMX:压缩的类空间最大容量(KB)
- CCSC:当前压缩的类空间大小(KB)
- YGC:年轻代GC事件的数量
- FGC:Full GC事件的数量
-
gccause:其他展示列和-gcutil一致
- LGCC:导致GC的原因
- GCC:导致当前GC的原因
-
gcnew:
- S0C:第一个存活区(S0)的容量(KB)
- S1C:第二个存活区(S1)的容量(KB)
- S0U:第一个存活区(S0)的利用率(KB)
- S1U:第二个存活区(S1)的利用率(KB)
- TT:老年代阈值
- MTT:最大老年代阈值
- DSS:期望的存活区大小(KB)
- EC:当前伊甸园容量(KB)
- EU:伊甸园利用率(KB)
- YGC:年轻代GC事件的数量
- YGCT:年轻代垃圾回收时间
-
gcnewcapacity:
-
NGCMN:最小年轻代容量(KB)
NGCMX:最大年轻代容量(KB)
NGC:当前年轻代容量(KB)
S0CMX:最大S0容量(KB)
S0C:当前S0容量(KB)
S1CMX:最大S1容量(KB)
S1C:当前S1容量(KB)
ECMX:最大伊甸园容量(KB)
EC:当前伊甸园容量(KB)
YGC:年轻代GC事件的数量
FGC:Full GC事件的数量
-
-
gcold:
- MC:当前元空间使用大小(KB)
- MU:元空间利用率(KB)
- CCSC:压缩的类的大小(KB)
- CCSU:使用的压缩类空间(KB)
- OC:当前的老年代空间容量(KB)
- OU:来年代空间利用率(KB)
- YGC:年轻代GC事件的数量
- FGC:Full GC事件的数量
- FGCT:Full GC垃圾收集时间
- GCT:总垃圾收集时间
-
gcoldcapacity:
- OGCMN:最小老年代容量(KB)
- OGCMX:最大老年代容量(KB)
- OGC:当前老年代容量(KB)
- OC:当前old space容量(KB)
- YGC:年轻代GC事件的数量
- FGC:Full GC事件的数量
- FGCT:Full GC垃圾收集时间
- GCT:总垃圾收集时间
-
gcmetacapacity:
- MCMN:最小元空间容量(KB)
- MCMX:最大元空间容量(KB)
- MC:元空间大小(KB)
- CCSMN:压缩的类空间最小容量(KB)
- CCSMX:压缩的类空间最大容量(KB)
- YGC:年轻代GC事件的数量
- FGC:Full GC事件的数量
- FGCT:Full GC垃圾收集时间
- GCT:总垃圾收集时间
-
gcutil:
- S0:第一个存活区(S0)利用率
- S1:第二个存活区(S1)利用率
- E:Eden空间利用率
- O:老年代空间利用率
- M:元空间利用率
- CCS:压缩的类空间利用率
- YGC:年轻代GC事件的数量
- YGCT:年轻代垃圾回收时间
- FGC:Full GC事件的数量
- FGCT:Full GC垃圾收集时间
- GCT:总垃圾收集时间
-
printcompilation:
- Compiled:由最近编译的方法去执行的编译任务数
- Size:最近编译的方法的字节码的字节数
- Type:最近编译的方法的编译类型。
- Method:标识最近编译的方法的类名和方法名。类名使用 / 代替点 . 作为名称空间分隔符;方法名称是指定类中的方法。这两个字段的格式与HotSpot
-XX:+PrintCompilation选项一致。
使用示例
示例1:查看21891这个进程的gc相关信息,每隔250ms采样1次,采样7次
shell
jstat -gcutil 21891 250 7
示例2:显示有关新生代行为的统计信息,重复列标题:
shell
jstat -gcnew -h3 21891 250
示例3:查看remote.domain机器上的40496这个进程有关垃圾收集统计信息的摘要,每隔1秒采样1次:
shell
jstat -gcutil 40496@remote.domain 1000
示例4:查看21891这个进程类加载器的统计信息,展示每次采样花费时间,且每采样3次后输出一次列标题,每隔1000ms监控一次,监控10次就退出工具
shell
jstat -class -t -h3 21891 1000 10
2-3 内置故障排查工具-jinfo
jinfo
作用
jinfo全称Java Configuration Info,主要用来查看与调整JVM参数。
TIPS
此命令是实验性的,不受支持,对于JDK 9及更高版本,部分功能可使用
jhsdb jinfo代替,也可用jcmd代替。部分JDK版本的jinfo命令对Windows支持比较有限,参数较少。本文为了更加接近生产环境,都是基于类Unix操作系统编写的。如果在Windows操作系统下测试,应以jinfo -h的结果为准。
参考文档:Java 8 Unix:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jinfo.html
JDK 8 Windows:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jinfo.html
Java 11:https://docs.oracle.com/en/java/javase/11/tools/jinfo.html
使用说明
命令如下:
shell
➜ jinfo -h
Usage:
jinfo <option> <pid>
(to connect to a running process)
where <option> is one of:
-flag <name> 打印指定参数的值
-flag [+|-]<name> 启用/关闭指定参数
-flag <name>=<value> 将指定的参数设置为指定的值
-flags 打印VM参数
-sysprops 打印系统属性(笔者注:系统属性打印的是System.getProperties()的结果)
<no option> 打印VM参数及系统属性
-? | -h | --help | -help to print this help message
使用示例
查看参数
示例1:打印42342这个进程的Java系统属性(其实就是代码中执行System.getProperties()的结果)就是项目里和JVM参数
shell
jinfo 42342
示例2:打印42342这个进程的Java系统属性
shell
jinfo -sysprops 42342
示例3:打印42342这个进程的JVM参数
shell
jinfo -flags 42342
示例4:打印42342这个进程ConcGCThreads参数的值
shell
jinfo -flag ConcGCThreads 42342
示例5:假设我们想要查看的某个参数没有在jinfo -flags 42342这个命令的结果里面展示,比如想要打印42342这个进程线程栈的大小
shell
jinfo -flag ThreadStackSize 42342
ps:具体想要查看的参数,可以到https://chriswhocodes.com这个地址查找
拓展知识
要想查看JVM参数,也可在启动时,指定
-XX:+PrintFlagsFinal,这样会在启动时将JVM参数打印到日志。
动态修改参数【比较有限】
示例5:将42342这个进程的PrintClassHistogram设置为false
shell
jinfo -flag -PrintClassHistogram 42342
示例6:将42342这个进程的MaxHeapFreeRatio设置为80
shell
jinfo -flag MaxHeapFreeRatio=80 42342
示例7:启动42342这个进程的HeapDumpAfterFullGC,+代表开启,-代表关闭,另外java -XX:+PrintFlagsInitial | grep manageable返回的第二列是true代表开启,false代表关闭
shell
jinfo -flag +HeapDumpAfterFullGC 42342
TIPS
虽然可用jinfo动态修改VM参数,但并非所有参数都支持动态修改,如果操作了不支持的修改的参数,将会报类似如下的异常:
shellException in thread "main" com.sun.tools.attach.AttachOperationFailedException: flag 'xxx' cannot be changed使用如下命令显示出来的参数,基本上都是支持动态修改的:
shelljava -XX:+PrintFlagsInitial | grep manageable
总结
通过以上命令,我们发现jinfo的能力还是比较强大的,为我们赋予了查看参数的能力,特别是查看JVM参数的能力,这样在定位JVM参数问题的时候就可以有据可依了,另外,jinfo还提供了动态修改JVM参数的能力,这样在做一些调优的时候,不需要重启应用就能生效了,为调优JVM提供了遍历
2-4 内置故障排查工具-jmap
jmap
作用
jmap全称Java Memory Map,用来展示对象内存映射或堆内存详细信息。
TIPS
此命令是实验性的,不受支持,对于JDK9及更高版本,部分功能可使用
jhsdb jmap代替,也可用jcmd代替。部分JDK版本的jmap命令对Windows支持比较有限,参数较少。本文为了更加接近生产环境,都是基于类Unix操作系统编写的。如果在Windows操作系统下测试,应以jmap -h的结果为准。
参考文档:Java 8 Unix:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jmap.html
JDK 8 Windows:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jmap.html
Java 11:https://docs.oracle.com/en/java/javase/11/tools/jmap.html
使用说明
命令如下:
shell
➜ jmap -h
Usage:
jmap -clstats <pid>
to connect to running process and print class loader statistics
jmap -finalizerinfo <pid>
to connect to running process and print information on objects awaiting finalization
jmap -histo[:live] <pid>
to connect to running process and print histogram of java object heap
if the "live" suboption is specified, only count live objects
jmap -dump:<dump-options> <pid>
to connect to running process and dump java heap
jmap -? -h --help
to print this help message
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
命令格式:
shell
jmap [options] pid
options的可选项如下:
- -clstats:连接到正在运行的进程,并打印Java堆的类加载器统计信息
- -finalizerinfo:连接到正在运行的进程,并打印等待finalization的对象的信息
- -histo[:live]:连接到正在运行的进程,并打印Java堆的直方图。如果指定了live子选项,则仅统计存活对象的直方图
- -dump:dump_options:连接到正在运行的进程,并转储Java堆(即将Java堆中的对象做一个快照存储到文件中,这个文件也叫堆Dump或者堆转储文件)。其中,dump_options的取值为:
- live:指定时,仅转储存活对象;如果未指定,则转储堆中的所有对象
- format=b:以hprof格式转储堆
- file=filename:将堆转储到filename
- 怎么分析这个文件filename,后续探讨
使用示例
shell
# 展示63120进程的类加载统计信息
jmap -clstats 63120
# 展示63120进程中等待finalization的对象的信息
jmap -finalizerinfo 63120
# 展示63120进程中堆的直方图
jmap -histo 63120
# 展示63120进程堆中存活对象的直方图
jmap -histo:live 63120
# Dump 63120这个进程中的存货对象的堆到dump.hprof文件
jmap -dump:live,format=b,file=dump.hprof 63120
拓展知识
要想获取Java堆Dump,除使用jmap外,还有以下方法:
- 使用-XX:+HeapDumpOnOutOfMemoryError,让虚拟机在OOM异常出现后自动生成堆Dump文件;
- 使用-XX:+HeapDumpOnCtrlBreak,可使用[Ctrl]+[Break],让虚拟机生成堆Dump文件;
- 在Linux操作系统下,发送
kill -3 pid命令;- 对于Spring Boot应用,也可以使用Spring Boot Actuator提供的/actuator/heapdump实现堆Dump。
2-5 内置故障排查工具-jstack
jstack
作用
jstack,全称Stack Trace for Java,用于打印当前虚拟机的线程快照(线程快照也叫Thread Dump或者javacore文件,其实就是当前虚拟机里面每个线程正在执行方法的堆栈的集合)。线程快照可以看到每个线程在做什么事情,和spring boot的Actuator threadump端点类似
TIPS
此命令是实验性的,不受支持,部分功能可用
jhsdb jstack代替。不同版本参数不同(JDK 8有-m、-F参数等,JDK 11都没了)
参考文档:Java 8 Unix:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstack.html
Java 8 Windows:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jstack.html
Java 11:https://docs.oracle.com/en/java/javase/11/tools/jstack.html
使用说明
命令如下:
shell
➜ jstack
Usage:
jstack [-l][-e] <pid>
(to connect to running process)
Options:
不带参数打印基本线程栈
-l 除不带参数的显示,还显示有关锁的额外信息
-e 除不带参数的显示和显示有关锁的额外信息,还展示有关线程的额外信息(比如分配了多少内存、定义了多少个类等等)
-? -h --help -help to print this help message
使用示例
shell
jstack 63120 > t1.txt
jstack -l 63120 > t1.txt
jstack -l -e 63120 > t1.txt
总结
在实际项目中,如果应用出现长时间等待时,可以考虑使用jstack,比如线程死锁,死循环,远程请求长时间得不到返回都可能会出现线程长时间等待,使用jstack可以看到每个线程的调用信息,这样就可以知道那些没有响应的线程在后台到底干了什么事情,或者正在等待什么资源
那么,人工分析jstack dump出来的线程还是比较累的,后续会探讨怎么可视化分析jstack dump出来的结果
2-6 内置故障排查工具-jhat
jhat
作用
jhat(JVM Heap Analysis Tool)用来分析jmap生成的堆Dump。
TIPS
此命令是实验性的,不受支持。
jhat功能不是很强,VisualVM、Eclipse Memory Analyzer等都比jhat强大,建议优先使用jhat的替代工具。
参考文档:Java 8 Unix:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jhat.html
Java 8 Windows:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jhat.html
Java 11:已废弃
使用说明
命令格式:
shell
jhat [options] heap-dump-file
options的可选项如下:
- -stack false | true:开启或关闭跟踪对象分配调用栈,默认true
- -refs false | true:开启或关闭对对象引用的跟踪,默认true
- -port port-number:指定jhat HTTP Server的端口,默认7000
- -exclude exclude-file:指定一个文件,该文件列出了应从可达对象查询中排除的数据成员。例如,如果文件包含java.lang.String.value,则对于指定对象o,不管对象列表针对o是否可达,都不会考虑涉及java.lang.String.value的引用路径
- -baseline exclude-file:指定基线堆Dump文件。两个堆Dunmp中,具有相同对象ID的对象都会标记为不是新对象,其他对象被标记为新对象。这对于比较两个不同的堆转储很有用。
- -debug intSets:指定该工具的debug级别。设置为0,则不会有debug输出。数值越高,日志越详细。
- -version:显示版本
使用示例
shell
# 分析1.hprof,并开启对象分配调用栈的分析
jhat -stack true 1.hprof
# 分析1.hprof,开启对象分配调用栈的分析,关闭对象引用的分析
jhat -stack true -refs false 1.hprof
等待片刻之后,访问 http://localhost:7000/ 即可查看分析结果。
2-7 内置故障排查工具-jcmd
jcmd
作用
jcmd全称JVM Command,正式支持的工具,用于将诊断命令请求发送到正在运行的Java虚拟机,从JDK 7开始提供。
参考文档:
Java 8 Unix:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jcmd.html
Java 8 Windows:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jcmd.html
Java 11:https://docs.oracle.com/en/java/javase/11/tools/jcmd.html
使用说明
命令如下:
shell
➜ jcmd -h
Usage: jcmd <pid | main class> <command ...|PerfCounter.print|-f file>
or: jcmd -l
or: jcmd -h
command must be a valid jcmd command for the selected jvm.
Use the command "help" to see which commands are available.
If the pid is 0, commands will be sent to all Java processes.
The main class argument will be used to match (either partially
or fully) the class used to start Java.
If no options are given, lists Java processes (same as -p).
PerfCounter.print display the counters exposed by this process
-f 从文件读取并执行命令
-l 列出本机上的所有JVM进程
-h this help 帮助文档
参数说明
- pid:接收诊断命令请求的进程ID。
- main class :接收诊断命令请求的进程的main类。jcmd会将诊断命令请求发送给具有指定main class的所有Java进程。
- command:command必须是一个有效的jcmd命令,可使用
jcmd pid help命令查看可用的命令列表。如果pid是0,那么command将会被发送给所有Java进程。main class会用来去匹配(局部匹配或全量匹配)。如果未指定任何选项,它将会列出正在运行的Java进程标识符以及用于启动该进程的main class和命令行参数(相当于使用了-l参数) - PerfCounter.print:打印指定Java进程上可用的性能计数器。
- -f filename:从指定文件中读取命令并执行。在file中,每个命令必须写在单独的一行。以"#"开头的行会被忽略。当所有行的命令被调用完毕后,或者读取到含有stop关键字的命令,将会终止对file的处理。
- -l:查看所有的JVM进程。jcmd不使用参数与jcmd -l效果相同。
使用示例
shell
# 查看所有JVM进程
jcmd -l
# 打印指定进程上可用的性能计数器
jcmd 26089 PerfCounter.print
# 打印所有启动类为com.imooc.Application的应用上可用的性能计数器
jcmd com.imooc.Application PerfCounter.print
# 打印指定进程的代码缓存的布局和边界
jcmd 26089 Compiler.codecache
支持的命令
1 help [options] [arguments]
- 作用:查看指定命令的帮助信息
- arguments:想查看帮助的命令(STRING,无默认值)
- options:选项,必须使用key或者key=value的预发指定,可用的options如下
- -all:(可选)查看所有命令的帮助信息(BOOLEAN,false)
使用示例:
shell
# 获得指定进程可用的命令列表
jcmd <pid> help
# 获取指定进程、指定命令的帮助信息,如果参数包含空格,需用'或者"引起来
jcmd <pid> help <command>
2 Compiler.codecache
- 作用:打印code cache(代码缓存)的布局和边界
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor)
3 Compiler.codelist
- 作用:打印代码缓存中所有仍在运行的已编译方法
- 影响:中
- 所需权限:
java.lang.management.ManagementPermission(monitor)
4 Compiler.queue
- 作用:打印排队等待编译的方法
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor)
5 Compiler.directives_add filename arguments
- 作用:从文件添加编译器指令
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor) - filename:指令文件的名称(STRING,无默认值)
6 Compiler.directives_clear
- 作用:删除所有编译器指令
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor)
7 Compiler.directives_print
- 作用:打印所有活动的编译器指令。
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor)
8 Compiler.directives_remove
- 作用:删除最新添加的编译器指令。
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor)
9 GC.class_histogram [options]
- 作用:提供有关Java堆使用情况的统计信息。
- 影响:高-取决于Java堆的大小和内容。
- 所需权限:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
- -all:(可选)检查所有对象,包括不可达的对象(BOOLEAN,false)
10 GC.class_stats [options] [arguments]
- 作用:展示有关Java类元数据的统计信息
- 影响:高-取决于Java堆的大小和内容。
- options:选项,必须使用key或者key=value的预发指定,可用的options如下:
- -all:(可选)显示所有列(BOOLEAN,false)
- -csv:(可选)以CSV格式打印电子表格(BOOLEAN,false)
- -help:(可选)显示所有列的含义(BOOLEAN,false)
- arguments:参数,可选参数如下:
- columns:(可选)要显示的列,以逗号分隔。如果不指定,则显示以下列:
- InstBytes
- KlassBytes
- CpAll
- annotations
- MethodCount
- Bytecodes
- MethodAll
- ROAll
- RWAll
- Total
- columns:(可选)要显示的列,以逗号分隔。如果不指定,则显示以下列:
使用示例:
shell
# 展示指定进程类的元数据的所有统计信息
jcmd 48758 GC.class_stats -all
# InstBytes、KlassBytes等列的含义
jcmd 48758 GC.class_stats -help
# 显示InstBytes,KlassBytes这两列,并生成csv
jcmd 48758 GC.class_stats -csv InstBytes,KlassBytes > t.csv
11 GC.finalizer_info
- 作用:展示有关Java finalization queue的信息。
- 影响:中
- 所需权限:
java.lang.management.ManagementPermission(monitor)
12 GC.heap_dump [options] [arguments]
相当于之前的jmap dump命令的效果
- 作用:生成Java堆Dump文件(HPROF格式)
- 影响:高-取决于Java堆的大小和内容。除非指定了
-all选项,否则将会导致Full GC - 所需权限:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
- -all:(可选)转储所有对象,包括不可达的对象(BOOLE AN,false)
- arguments:参数,可用的参数如下:
- filename:Dump文件的名称(STRING,无默认值)
使用示例:
shell
jcmd 48758 GC.heap_dump -all 1.hprof
13 GC.heap_info
- 作用:展示Java堆信息。
- 影响:中
- 所需权限:
java.lang.management.ManagementPermission(monitor)
14 GC.run
- 作用:调用
java.lang.System.gc(),从而通知JVM作一次垃圾回收 - 影响:中-取决于Java堆的大小和内容
15 GC.run_finalization
- 作用:
java.lang.System.runFinalization() - 影响:中-取决于Java内容。
16 JFR.check [options]
请参阅《Java Flight Recorder命令参考》中的JFR.check。
17 JFR.configure [options]
请参阅《Java Flight Recorder命令参考》中的JFR.configure。
18 JFR.dump [options]
请参阅《Java Flight Recorder命令参考》中的JFR.dump。
19 JFR.start [options]
请参阅《Java Flight Recorder命令参考》中的JFR.start。
20 JFR.stop [options]
请参阅《Java Flight Recorder命令参考》中的JFR.stop。
21 JVMTI.agent_load [arguments]
- 作用:加载JVMTI本机代理。
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(control) - arguments:
- library path:要加载的JVMTI代理的绝对路径(STRING,无默认值)
- agent option:(可选)用于传递代理的选项字符串(STRING,无默认值)
22 JVMTI.data_dump
- 作用:通知JVM对JVMTI进行数据转储
- 影响:高
- 所需权限:
java.lang.management.ManagementPermission(monitor)
23 ManagementAgent.start [options]
- 作用:启动远程管理代理
- 影响:低-无影响
- options:选项,必须使用key或者key=value的预发指定,可用的options如下:
config.file:(可选)设置com.sun.management.config.file(STRING,无默认值)jmxremote.host:(可选)设置com.sun.management.jmxremote.host(STRING,无默认值)jmxremote.port:(可选)设置com.sun.management.jmxremote.port(STRING,无默认值)jmxremote.rmi.port:(可选)设置com.sun.management.jmxremote.rmi.port(STRING,无默认值)jmxremote.ssl:(可选)设置com.sun.management.jmxremote.ssl(STRING,无默认值)jmxremote.registry.ssl:(可选)设置com.sun.management.jmxremote.registry.ssl(STRING,无默认值)jmxremote.authenticate:(可选)设置com.sun.management.jmxremote.authenticate(STRING,无默认值)jmxremote.password.file:(可选)设置com.sun.management.jmxremote.password.file(STRING,无默认值)jmxremote.access.file:(可选)设置com.sun.management.jmxremote.access.file(STRING,无默认值)jmxremote.login.config:(可选)设置com.sun.management.jmxremote.login.config(STRING,无默认值)jmxremote.ssl.enabled.cipher.suites:(可选)集com.sun.management。jmxremote.ssl.enabled.cipher.suite:(STRING,无默认值)jmxremote.ssl.enabled.protocols:(可选)设置com.sun.management.jmxremote.ssl.enabled.protocols(STRING,无默认值)jmxremote.ssl.need.client.auth:(可选)设置com.sun.management.jmxremote.need.client.auth(STRING,无默认值)jmxremote.ssl.config.file:(可选)设置com.sun.management.jmxremote.ssl_config_file(STRING,无默认值)jmxremote.autodiscovery:(可选)设置com.sun.management.jmxremote.autodiscovery(STRING,无默认值)jdp.port:(可选)设置com.sun.management.jdp.port(INT,无默认值)jdp.address:(可选)设置com.sun.management.jdp.address(STRING,无默认值)jdp.source_addr:(可选)设置com.sun.management.jdp.source_addr(STRING,无默认值)jdp.ttl:(可选)设置com.sun.management.jdp.ttl(INT,无默认值)jdp.pause:(可选)设置com.sun.management.jdp.pause(INT,无默认值)jdp.name:(可选)设置com.sun.management.jdp.name(STRING,无默认值)
24 ManagementAgent.start_local
- 作用:启动本地管理代理
- 影响:低-无影响
25 ManagementAgent.status
- 作用:展示管理代理的状态
- 影响:低-无影响
- 所需权限:
java.lang.management.ManagementPermission(monitor)
26 ManagementAgent.stop
- 作用:停止远程管理代理
- 影响:低-无影响
27 Thread.print [options]
类似于之前的jstack命令的效果
- 作用:打印所有带有堆栈跟踪的线程。
- 影响:中-取决于线程数。
- 所需权限:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
- -l:(可选)打印
java.util.concurrent锁(BOOLEAN,false)
- -l:(可选)打印
使用示例:
shell
jcmd 48758 Thread.print -l
28 VM.check_commercial_features
- 作用:显示商业特性的状态
- 影响:低-无影响
29 VM.unlock_commercial_features
- 作用:解锁商业功能
- 影响:低-无影响
- 所需权限:
java.lang.management.ManagementPermission(control)
30 VM.classloader_stats
- 作用:打印所有ClassLoader的统计信息。
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor)
31 VM.class_hierarchy [options] [arguments]
- 作用:打印所有已加载类的列表,缩进以显示类层次结构。每个类的名称后跟其ClassLoader的
ClassLoaderData*,如果由bootstrap class loader加载,则为null - 影响:中-取决于已加载类的数量。
- 所需权限:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
-i:(可选)打印继承的接口(BOOLEAN,false)-s:(可选)如果指定了类名,则将打印子类。如果未指定类名,则仅打印超类(BOOLEAN,false)
- arguments:参数,可用项如下:
- classname:(可选)打印指定类的层次结构,如果未指定,则将打印所有类层次结构(STRING,无默认值)
使用示例:
shell
jcmd 48758 VM.class_hierarchy -i -s javax.servlet.GenericFilter
32 VM.command_line
- 作用:打印用于启动此VM实例的命令行
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor)
33 VM.dynlibs
- 作用:打印加载的动态库
- 影响:低
- 允许:
java.lang.management.ManagementPermission(monitor)
34 VM.info
- 作用:打印有关JVM环境和状态的信息
- 影响:低
- 允许:
java.lang.management.ManagementPermission(monitor)
35 VM.log [options]
- 作用:列出当前日志配置,启用/禁用/配置日志输出,或轮换所有日志
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(control) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
output:(可选)要配置的输出的名称或索引。(STRING,无默认值)output_options:(可选)输出的选项。(STRING,无默认值)what:(可选)配置要记录的标签。(STRING,无默认值)decorators:(可选)配置要使用的装饰器。使用'none'或空值删除所有内容。(STRING,无默认值)disable:(可选)关闭所有日志记录并清除日志配置。(布尔值,无默认值)list:(可选)列出当前的日志配置。(布尔值,无默认值)rotate:(可选)轮换所有日志。(布尔值,无默认值)
使用示例:
shell
jcmd 48758 VM.log output what
36 VM.flags [options]
- 作用:打印VM标志及其当前值。
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的语法指定,可用的options如下:
- -all:(可选)打印VM支持的所有标志(BOOLEAN,false)
37 VM.native_memory [options]
TIPS
译者注:该功能叫做"Native Memory Tracking (NMT)",需开启如下参数,才可打开。
-XX:NativeMemoryTracking=[off | summary | detail]
打开后会带来5-10%的性能损耗。
也可用
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics,让JVM在退出时打印NMT报告。
- 作用:打印native内存使用情况
- 影响:中
- 允许:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
- summary:(可选)请求运行时报告当前内存摘要,包括所有保留和提交的内存以及每个子系统的内存使用情况摘要(BOOLEAN, false)
- detail:(可选)请求运行时报告每个调用站点(callsite) > = 1K的内存分配(BOOLEAN, false)
- baseline:(可选)请求运行时以当前内存使用情况为基准,以便以后进行比较(BOOLEAN, false)
- summary.diff:(可选)请求运行时报告与先前基准的内存摘要比较(BOOLEAN, false)
- detail.diff :(可选)请求运行时报告与先前基准的内存详情比较,该基准显示了在不同调用站点(callsite)的内存分配活动(BOOLEAN, false)
- shutdown:(可选)请求运行时关闭自身并释放运行时使用的内存(BOOLEAN, false)
- statistics:(可选)打印跟踪器统计信息以进行调整(BOOLEAN, false)
- scale:(可选)以MB,MB或GB为单位的内存使用量(STRING, KB )
38 VM.print_touched_methods
- 作用:打印此JVM生命周期中曾经接触过的所有方法
- 影响:中-取决于Java内容
39 VM.set_flag [arguments]
- 作用:设置VM标志
- 影响:低
- 所需权限:
java.lang.management.ManagementPermission(control) - arguments:
- 标志名称:您要设置的标志名称(STRING,无默认值)
- 字符串值:(可选)要设置的值(STRING,无默认值)
40 VM.stringtable [options]
- 作用:转储字符串表(string table)
- 影响:中-取决于Java内容。
- 所需权限:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
- -verbose:(可选)转储表中每个字符串的内容(BOOLEAN,false)
使用示例:
shell
jcmd 48758 VM.stringtable -verbose
41 VM.symboltable [options]
- 作用:转储符号表
- 影响:中-取决于Java内容
- 所需权限:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
-verbose:(可选)转储表中每个符号的内容(BOOLEAN,false)
使用示例:
shell
jcmd 48758 VM.symboltable -verbose
42 VM.systemdictionary
- 作用:打印字典哈希表大小和存储桶长度的统计信息
- 影响:中
- 所需权限:
java.lang.management.ManagementPermission(monitor) - options:选项,必须使用key或者key=value的预发指定,可用的options如下:
verbose:(可选)为所有class loader转储每个词典条目的内容(BOOLEAN,false)。
使用示例:
shell
jcmd 48758 VM.systemdictionary -verbose
43 VM.system_properties
- 作用:打印系统属性
- 影响:低
- 所需权限:
java.util.PropertyPermission(*, read)
44 VM.uptime [options]
- 作用:打印虚拟机的运行时间
- 影响:低
- options:选项,必须使用key或者key=value的预发指定,可用的options如下:
-date:(可选)添加带有当前日期的前缀(BOOLEAN,false)
45 VM.version
- 作用:打印JVM版本信息
- 影响:低
- 所需权限:
java.util.PropertyPermission(java.vm.version, read)
2-8 内置故障排查工具-jhsdb
jhsdb
Jhsdb全称Java Hotspot Debugger,Hotspot进程调试器,可用于从崩溃的JVM附加到Java进程或核心转储。
jhsdb是一款基于Serviceability Agent(可维护性代理,简写为SA)的调试工具。Serviceability Agent是一个JDK组件,用于快照调试、性能分析以及深入了解Hotspot JVM / Hotspot JVM上执行的Java应用程序。
它的工作原理有点类似于Linux上的GDB或者Windows上的Windbg。但尽管诸如gdb的本机调试器可用于检查JVM,但这类本机调试器对Hotspot中的数据结构没有内在了解,因此无法对正在执行的Java应用程序进行深入了解。jhsdb了解JVM关键组件(例如Java堆,堆的代,region,代码缓存等)的位置和地址范围。
参考文档
- Java 8:无,Java 9才正式引入。
- Java 11:https://docs.oracle.com/en/java/javase/11/tools/jhsdb.html
TIPS尽管JDK 8及更低版本不直接提供jhsdb命令,但依然其实也是可以使用jhsdb的,只需找到
JAVA_HOME/lib目录下的sa-jdi.jar文件,然后启动即可。步骤如下:
shell# 修改环境变量JAVA_HOME(这里用export临时修改环境变量,当然也可永久修改) export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home" # 为sa-jdi.jar授予执行权限 sudo chmod +x $JAVA_HOME/lib/sa-jdi.jar # 启动方式1:使用交互式命令行调试器(相当于jhsdb clhsdb) java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB # 启动方式2:使用交互式GUI调试器启动jhsdb(相当于jhsdb hsdb) java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
使用说明
shell
# 启动交互式命令行调试器
jhsdb clhsdb [--pid pid | --exe executable --core coredump]
# 启动远程调试服务器
jhsdb debugd [options] (pid | executable coredump) [server-id]
# 启动交互式GUI调试器
jhsdb hsdb [--pid pid | --exe executable --core coredump]
# 打印堆栈并锁定信息
jhsdb jstack [--pid pid | --exe executable --core coredump] [options]
# 打印堆信息
jhsdb jmap [--pid pid | --exe executable --core coredump] [options]
# 打印基本的JVM信息
jhsdb jinfo [--pid pid | --exe executable --core coredump] [options]
# 打印性能计数器信息
jhsdb jsnap [options] [--pid pid | --exe executable --core coredump]
其中:
-
pid:进程号
-
server-id:当多个调试服务器在同一远程主机上运行时使用的可选唯一ID
-
executable:从中生成核心转储的Java可执行文件
-
coredump:jhsdb工具连接到的Dump文件
coredump介绍与开启方式:https://www.cnblogs.com/Anker/p/6079580.html
-
options:命令行选项,和子命令有关。
TIPS
--pid、--exe参数二选一必填
options说明
jhsdb clhsdb子命令
- 无
示例:
shell
# 进入clhsdb
jhsdb clhsdb --pid 81033
进入交互界面后,输入help,查看相关的命令,命令如下:
Available commands:
assert true | false turn on/off asserts in SA code
attach pid | exec core attach SA to a process or core
buildreplayjars [all | boot | app] build jars for replay, boot.jar for bootclasses, app.jar for application classes
class name find a Java class from debuggee and print oop
classes print all loaded Java classes with Klass*
detach detach SA from current target
dis address [ length ] disassemble (sparc/x86) specified number of instructions from given address
dissemble address disassemble nmethod
dumpcfg -a | id Dump the PhaseCFG for every compiler thread that has one live
dumpclass { address | name } [ directory ] dump .class file for given Klass* or class name
dumpcodecache dump codecache contents
dumpheap [ file ] dump heap in hprof binary format
dumpideal -a | id dump ideal graph like debug flag -XX:+PrintIdeal
dumpilt -a | id dump inline tree for C2 compilation
dumpreplaydata
| -a | [>replay.txt] dump replay data into a file
echo [ true | false ] turn on/off command echo mode
examine [ address/count ] | [ address,address] show contents of memory from given address
field [ type [ name fieldtype isStatic offset address ] ] print info about a field of HotSpot type
findpc address print info. about pointer location
flags [ flag ] show all -XX flag name value pairs. or just show given flag
help [ command ] print help message for all commands or just given command
history show command history. usual !command-number syntax works.
inspect expression inspect a given oop
intConstant [ name [ value ] ] print out hotspot integer constant(s)
jdis address show bytecode disassembly of a given Method*
jhisto show Java heap histogram
jseval script evaluate a given string as JavaScript code
jsload file load and evaluate a JavaScript file
jstack [-v] show Java stack trace of all Java threads. -v is verbose mode
livenmethods show all live nmethods
longConstant [ name [ value ] ] print out hotspot long constant(s)s
mem address [ length ] show contents of memory -- also shows closest ELF/COFF symbol if found
pmap show Solaris pmap-like output
print expression print given Klass*, Method* or arbitrary address
printas type expression print given address as given HotSpot type. eg. print JavaThread <address>
printmdo -a | expression print method data oop
printstatics [ type ] print static fields of given HotSpot type (or all types if none specified)
pstack [-v] show mixed mode stack trace for all Java, non-Java threads. -v is verbose mode
quit quit CLHSDB tool
reattach detach and re-attach SA to current target
revptrs find liveness of oops
scanoops start end [ type ] scan a Oop from given start to end address
search [ heap | codecache | threads ] value search a value in heap or codecache or threads
source filename load and execute CLHSDB commands from given file
symbol name show address of a given ELF/COFF symbol
sysprops show all Java System properties
thread id show thread of id
threads show all Java threads
tokenize ...
type [ type [ name super isOop isInteger isUnsigned size ] ] show info. on HotSpot type
universe print gc universe
vmstructsdump dump hotspot type library in text
verbose true | false turn on/off verbose mode
versioncheck [ true | false ] turn on/off debuggee VM version check
whatis address print info about any arbitrary address
where { -a | id } print Java stack trace of given Java thread or all Java threads (-a)
TIPS
不同版本支持的命令可能不同,这里列出的命令可能你的JVM并不一定能支持。
参考文档:
- http://cr.openjdk.java.net/\~minqi/6830717/raw_files/new/agent/doc/clhsdb.html
- https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/jdk.hotspot.agent/doc/clhsdb.html
jhsdb hsdb子命令
clhsdb子命令是基于命令行的,而且子命令很多,使用相对复杂。hsdb子命令功能基本上和clhsdb子命令等价,但是有图形化界面,使用起来直观!一定程度上降低了clhsdb子命令的使用难度
- 无
示例:
shell
# 图形化模式,和clhsdb功能对标
jhsdb hsdb --pid 81033
有关该子命令,详见《jhsdb hsdb》一文。
jhsdb jinfo子命令
jinfo子命令和之前讲解的jinfo命令功能上基本一样,但是它无法动态修改JVM参数
- --flags:打印VM标志
- --sysprops:打印Java系统属性
- 留空:打印VM标志和Java系统属性
示例:
shell
# 打印80904进程的VM标志
jhsdb jinfo --flags --pid 80904
# 打印80904进程的系统属性
jhsdb jinfo --sysprops --pid 80904
jhsdb jmap子命令
jmap子命令和之前讲解的jmap命令功能上基本一样
- --heap:打印Java堆的概要信息
- --binaryheap:将Java堆以hprof格式Dump出来
- --dumpfile:执行dump文件名
- --histo:打印Java堆的直方图
- --clstats:打印Java堆的类加载器统计信息
- --finalizerinfo:打印等待finalization的对象的信息
示例:
shell
# 打印81033进程Java堆的直方图
jhsdb jmap --histo --pid 81033
# 将81033进程的Java堆dump到2.hprof
jmap --binaryheap --dumpfile 2.hprof --pid 81033
jhsdb jstack子命令
jstack子命令和之前讲解的jstack命令功能上基本一样
- --locks:打印java.util.concurrent锁的信息
- --mixed:尝试打印Java栈与本地方法栈的信息(需操作系统支持)
示例:
shell
# 打印81033锁的信息,并尝试打印Java栈与本地方法栈的信息
jhsdb jstack --locks --mixed --pid 81033
jhsdb jsnap子命令
- --all:打印所有性能计数器的信息
示例:
shell
jhsdb jsnap --all --pid 81033
相当于:
shell
jcmd 81033 PerfCounter.print
jhsdb debugd子命令
- server-id:当多个调试服务器在同一远程主机上运行时使用的可选唯一ID
示例:
shell
jhsdb debugd 81033
TIPS
debugd子命令的格式和其他子命令不同,这是个bug,在JDK 13中已经和其他子命令保持一致了。
jhsdb和其他工具的对比
| 功能 | JHSDB | JCMD | 类似工具 |
|---|---|---|---|
| 展示Java进程 | N/A |
jcmd |
jps -lm |
| 堆Dump | jhsdb jmap --binaryheap |
jcmd pid GC.heap_dump |
jmap -dump pid |
| 堆使用直方图 | jhsdb jmap --histo |
jcmd pid GC.class_histogram |
jmap -histo pid |
| 线程Dump | jhsdb jstack --locks (subset of locked thread frames) |
jcmd pid Thread.print |
jstack pid |
| 展示系统属性 | jhsdb jinfo --sysprops |
jcmd pid VM.system_properties |
jinfo -sysprops pid |
| 列出VM标记 | jhsdb jinfo --flags |
jcmd pid VM.flags |
jinfo -flags pid |
macOS下遇到的问题
在macOS下,目前最新的11.0.7中的jhsdb无法正常使用,会报类似如下异常:
Error attaching to process: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 11.0.7+8-LTS. Target VM is 11.0.4+10-LTS
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 11.0.7+8-LTS. Target VM is 11.0.4+10-LTS
at jdk.hotspot.agent/sun.jvm.hotspot.HotSpotAgent.setupVM(HotSpotAgent.java:436)
at jdk.hotspot.agent/sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:306)
at jdk.hotspot.agent/sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:141)
at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:185)
at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:118)
at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:259)
at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:450)
这是JDK版本的问题(JDK bug,只在macOS下出现),只需将JDK降级至11.0.4即可,如果还报错,可能是因为测试的进程是用11.0.7版本的JDK启动的,改用JDK11.0.4启动即可,也可在Linux或Windows下测试jhsdb工具。
JDK 11.0.4下载地址:
-
https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html
-
百度加速下载:
shell链接:https://pan.baidu.com/s/1HXjzNDpzin6fXGrZPyQeWQ 密码:aon2
安装完成后,先用JDK 11.0.4启动一个应用,然后即可使用jhsdb工具调试该应用了。
参考文档
https://dzone.com/articles/jhsdb-a-new-tool-for-jdk-9
2-9 内置可视化工具-jhsdb hsdb
jhsdb hsdb
TIPS
- jhsdb hsdb是jhsdb的子命令,故而阅读本文之前,请务必先阅读《jhsdb》
- 本文基于JDK 11编写,理论适用于JDK 9及更高版本,对于JDK 8及更低版本,可参照《jhsdb》中的描述使用。
本文接演《jhsdb》,探讨jhsdb hsdb子命令,也就是jhsdb的GUI模式。
要想进入,只需输入如下命令:
shell
jhsdb hsdb [--pid pid | --exe executable --core coredump]
示例:
shell
jhsdb hsdb --pid 81033
Java Threads
Java Threads

进入后,可看到类似如上图的界面:
其中:
- OS Thread ID:线程id
- Java Thread Name:线程名称
Inspect Thread
选择一个线程,然后点击 Java Thread 对话框中的第一个图标,即可弹出 Inspector 对话框,从中可看到对线程的诊断信息。
其中展示了对象头和指向对象元数据的指针,里面包括了Java类型的名字、继承关系、实现接口关系,字段信息、方法信息、运行时常量池的指针、内嵌的虚方法表(vtable)以及接口方法表(itable)等。其实都是HotSpot VM里记录线程的一些基本信息的C++对象的内容。 如果对C++不熟悉,阅读起来会有一些困难。

Stack Memory
选择一个线程,然后点击 Java Thread 对话框中的第二个图标,即可弹出 Stack Memory 对话框,里面是线程栈的内存数据。

其中:
- 第一列:内存地址(虚拟地址,非物理内存地址)
- 第二列:该地址上存储的数据,以字宽为单位,本文是在macOS 64-bit上跑64位的HotSpot VM JDK 11,字宽是64位(8字节)
- 第三列是:对数据的注释,竖线表示范围,横线或斜线连接范围与注释文字。
如果想查看某个对象的内容,可如下操作:
-
Windows - Console
-
输入
shellinspect 想要查看对象的地址
如下图所示:

Show Java stack trace
选择一个线程,然后点击 Java Thread 对话框中的第三个图标,即可弹出 Show Java stack trace 对话框,里面展示了这个线程的线程栈跟踪信息,和之前的jstack命令类似,还可以点击蓝色字体查看更多信息

Show Thread Information
选择一个线程,然后点击 Java Thread 对话框中的第四个图标,即可弹出 Show Thread Information 相关信息,里面展示了这个线程的信息。

特别注意的是线程的状态,State,一个线程存在以下这些状态,BLOCKED,表示这个线程在JVM中已经被阻塞

Find Crashes
选择一个线程,然后点击 Java Thread 对话框中的第五个图标,即可找到该线程崩溃的原因。目前我这边线程没有崩溃,会显示No thread crashes encountered,所以展示不出效果。
Windows Console
又出现一个对话框。然后输入诊断命令,输入help可查看所有诊断命令。和jhsdb clhsdb里面的诊断命令一致。
Tools
提供一系列工具,这些工具其实是jhsdb诊断命令的一个便捷化,也都可以用诊断命令代替。
收集的HSDB相关的文章
TIPS
由于这些文章使用的JDK版本较老,里面用到的部分命令已经被废弃了,所以并不能直接使用,这些文章用于拓展思路、仅供参考。
实验
使用JDK11.0.4启动下面的类,然后使用jhsdb调试,分析staticObj、instanceObj、localObj分别存储在内存的哪个区域
java
package com.imooc.jvm.jhsdb;
public class JHSDBTest {
static class Test {
static SomeObject staticObj = new SomeObject();
SomeObject instanceObj = new SomeObject();
void foo() {
SomeObject localObj = new SomeObject();
System.out.println("done"); // 断点
}
}
private static class SomeObject {
private Short age;
private String name;
}
public static void main(String[] args) {
Test test = new JHSDBTest.Test();
test.foo();
}
}
启动前设置参数如下

-XX:+UseSerialGC表示使用串行GC,为了让测试更加简单,-XX:-UseCompressedOops表示关闭压缩指针,压缩指针是指对象的地址是由指针表示,对象的指针越大占用的内存越大,64位JVM的指针比32位JVM的指针占用的内存要大一些,Java为了降低64位JDK内存的开销从而提升性能就提供压缩指针的技术,让64位的JDK把指针压缩起来,那么,这里之所以关闭压缩指针,是因为jhsdb对压缩指针支持并不好,实际项目中,从节约内存以及性能角度来说,建议打开压缩指针
使用jhsdb hsdb命令分析该进程

如图,除了main方法线程以外其它线程都是JVM内置的,比如Finalizer线程负责垃圾回收
点击Tools->Heap Parameters可以查看堆内存信息

如图,Gen 0代表伊甸园,from代表from survivor,to代表To survivor,Gen 1代表老年代,就目前来说,from survivor,To survivor,老年代使用都是0,而代码在这里(断点处)new了三个对象,因此推测出,一般情况下,对象在初始化时,都是在伊甸园里面分配的,符合前面的理论知识
点击Windows->Console,scanoops 指针地址

【scanoops 0x0000000129e00000 0x000000012a80da98】,0x0000000129e00000代表伊甸园的起始地址,0x000000012a80da98代表伊甸园的结束地址,查看这个范围中有哪些对象。可以看到里面确实找到了new的几个对象,并且展示了对象的地址
接着,看下这三个SomeObject实例对象信息,点击Tools->Inspector,输入地址0x000000012a770c20

可以看到0x000000012a770c20这个SomeObject实例对象信息,是staticObj,是一个静态变量,并且从Oop for java/lang/Class这行可以看出,这个实例是被类引用的,所以存储在方法区
输入地址0x000000012a770c58

可以看到0x000000012a770c58这个实例是被JHSDBTest$Test所引用的,可以看到这个对象的名称为instanceObj,对应代码的SomeObject instanceObj = new SomeObject();
输入地址0x000000012a770c78

可以看到0x000000012a770c78对象是被main方法的栈引用的,说明是局部变量,并且这个对象是一个根对象(stack root)
通过分析,可以推断,0x000000012a770c20挂在类上的,存储在方法区。0x000000012a770c58存储在堆内存。0x000000012a770c78存储在main方法栈帧的局部变量表中
同样,也可以使用命令的方式调试,点击Windows->Console,输入revptrs 0x000000012a770c20(命令全称对应之前控制台调试的Reverse pointers),这样就可以看到这个对象被哪里引用了

为什么查看0x000000012a770c78这个变量显示null,因为revptrs命令不支持查找栈上的指针引用,对于栈上的指针引用可以用上面介绍控制台Inspector的方式,也可以到对应的线程上面点击Stack Memory

如图,0x000000012a770c78这个实例是存储在新生代的,类型是com.imooc.jvm.jhsdbJHSDBTest&SomeObject
总结
通过实验,论证了几个事情,首先,论证了静态变量、成员变量、局部变量存储在哪里;论证了对象初始化的时候一般是在伊甸园里分配的,同时还练习了jhsdb的图形化模式,当然,这个例子比较简单,实际开发需要多测试看看!
2-10 内置可视化工具-jconsole
jconsole
JConsole(Java Monitoring and Management Console)是一款基于JMX(Java Manage-ment Extensions)的可视化监控、管理工具。它主要通过JMX的MBean(Managed Bean)对系统进行信息收集和参数动态调整。
JMX是一种开放性的技术,它既可以用在虚拟机本身的管理上,也可以用于运行在虚拟机之上的软件中。目前很多软件都支持基于JMX进行管理与监控。
启动
执行如下命令:
shell
jconsole
就会弹出jconsole一个新建连接的窗口。

- 对于本地JVM进程,jconsole会自动搜索,无需用户人工指定;
- 如果想要连接远程进程,请参见后续《远程连接》相关的内容
使用
选择39548进程点击连接后,即可看到jconsole的使用窗口了,如下图所示。

- 概览:展示虚拟机运行数据的概要信息,包括堆内存使用量、线程、类、CPU占用率的曲线图。这些曲线图本质上是内存、线程、类等几个页面的信息汇总。
- 内存:用于监控虚拟机内存的变化趋势,相当于可视化的jstat命令。
- 线程:监控应用线程的个数波动及状态,当遇到线程停顿的时候可以考虑用这个页面的功能进行分析,相当于可视化的jstack命令。
- 类:监控应用加载的类的变化趋势。
- VM概要:展示应用的一些概要信息。比如虚拟机版本、虚拟机供应商、虚拟机进程号、当前活动线程等
- MBean:展示应用被JMX管理的Bean。
2-11 内置可视化工具-VisualVM
VisualVM
TIPS
- 本文基于VisualVM 2.0.1编写
- 不同版本的VisualVM特性有所不同(例如VisualVM 2.0+提供了对JDBC的性能分析),界面甚至也会有一定差异,因此,建议使用和笔者相同的版本测试。
官方说VisualVM是一个All-in-One Java Troubleshooting Tool,从JDK 6开始提供,是目前最强大的监控及故障处理程序之一。
启动
JDK 8或更低版本
对于JDK 8及更低版本,JDK内置了VisualVM。只需执行如下命令即可启动
shell
jvisualvm
JDK 9及更高版本
对于JDK 9及更高版本,VisualVM默认不再内置,而是作为一个独立项目维护,因此需要手动下载。独立下载的VisualVM,最低支持JDK 8。
因此,需自行下载。前往 https://visualvm.github.io/download.html 即可下载。其中:
- zip文件:可适用于各种操作系统
- dmg文件:可适用于macOS
建议下载zip文件。这里也提供百度盘加速下载地址:
shell
链接:https://pan.baidu.com/s/1HXjzNDpzin6fXGrZPyQeWQ 密码:aon2
下载完成后,解压,并按操作系统启动:
-
对于非Windows系统:运行如下命令即可启动
shell${visualvm目录}/bin/visualvm -
对于Windows系统:执行如下文件即可。
shell${visualvm目录}/bin/visualvm.exe
使用
和jconsole类似,VisualVM也会自动带出当前启动的本地进程,当然,也可以远程连接,远程连接后续介绍
- Overview:展示应用的概要信息,相当于可视化的jps、jinfo
- Monitor:监控
- 图表:展示CPU、内存、类、线程等曲线图
- Perform GC:通知JVM执行垃圾回收
- Heap Dump:Dump堆,相当于jmap dump命令。点击后,在左侧的heapdump节点上右击另存为,可将其存储为文件。
- Threads:查看线程状态。绿色表示线程正在运行,紫色表示线程已经sleeping。Running栏表示线程运行状态下花费多久,Total表示线程从创建到现在花费多久
- Thread Dump:Dump线程,相当于jstack。点击后,在左侧的threaddump节点上右击另存为,可将其存储为文件。
- Sampler:抽样器,可用于实时性能分析
- CPU抽样:可展示每个线程花费的CPU时间、分析热点方法等
- 内存抽样:展示堆直方图、每个线程的内存分配
- Profiler:性能分析,提供了程序运行期方法级的处理器执行时间分析及内存分析
- CPU性能分析、内存性能分析、JDBC性能分析
- 还可以配置想检查的范围。
- 注意点:
- 执行性能分析,会对程序运行性能有比较大的影响,一般不建议在生产环境使用这项功能。可在开发/测试环境去分析并调优,也可用JMC代替,JMC的性能分析能力更强,而且影响相对小很多
- 类共享(类共享是一种共享类,从而提升加载速度、节省内存的技术)可能会导致执行Profiler的应用崩溃,建议在执行Profiler的应用上添加-Xshare:off,关闭掉类共享。
拓展知识
OQL语法:https://blog.csdn.net/pange1991/article/details/82023771
插件
- Tools - Plugins - 安装插件
- https://github.com/oracle/visualvm/releases,手动下载插件
分析堆Dump文件
- File -Load - 选择hprof - 打开 - 分析
参考文档
- https://blog.csdn.net/localhost01/article/details/83422902
- https://jingyan.baidu.com/article/f3e34a12d39fd9f5eb65350e.html
- https://www.cnblogs.com/xifengxiaoma/p/9402497.html
- https://www.pianshen.com/article/644690794/
- https://blog.csdn.net/u013970991/article/details/52036253
2-12 内置可视化工具-JDK Mission Control
JDK Mission Control
TIPS
本文基于JDK Mission Control 7.0.1编写
不同版本的JDK Mission Control特性、功能甚至界面都可能不同,因此建议使用和笔者相同的版本测试(例如7.0.1版本的界面和JDK 8内置的JMC 5.5界面简直不像是一个软件)
有关商业授权的说明
:
- JMC曾经是一款商业授权工具(例如在JDK 8中),需要商业授权才能在生产环境中使用。但根据Oracle Binary Code协议,在个人开发环境中可以免费使用。
- 现已开源,在JDK 11(哪怕是OpenJDK)中,任何人都可以使用 JFR + JMC(需遵循 UPL协议 )!
- 本文只调研了JDK的长期支持版本JDK 8/JDK 11的商业授权规则,没有去细究短期支持版本JDK 9、10等短期支持版本的商业授权规则(主要是生产环境应该没人敢用短期支持的JDK版本)。
JDK Mission Control也叫Java Mission Control,简称JMC。
JMC的两大功能:
- 作为JMX控制台,监控虚拟机MBean提供的数据
- 可持续收集数据的JFR(Java Flight Recorder),并可作为JFR的可视化分析工具
在正式探讨JMC之前,有必要先聊下JFR。
JFR(Java Flight Recorder)是一种用于收集有关运行中的Java应用的诊断信息和性能数据的工具。它几乎没有性能开销,因此,即使在负载很大的生产环境中也可以使用。并且是随用随停的,JFR主要用于以下场景:
-
性能分析
JFR可连续捕获应用的信息。比如执行概要分析(显示程序花费时间的地方),线程停顿/等待时间概要分析(显示线程为什么不运行的原因),分配概要分析(显示分配压力的位置),垃圾回收详细信息等
-
黑盒分析
由于JFR开销非常低,因此可持续打开Flight Recorder,让JFR将信息保存到缓存区,然后在稍后再去分析这块数据,定位特定异常的原因
-
支持与调试
联系Oracle支持人员寻求帮助时,JFR收集到的数据可能至关重要
安装JMC
对于JDK 10及更低版本
对于JDK 10及更低版本,JDK内置了JMC,使用如下命令启动:
shell
jmc
TIPS
笔者测试,发现JDK 8中内置的JMC在macOS下无法正常启动,这是由于JAR包冲突导致的。解决方案有两种:
- 参照 https://stackoverflow.com/questions/48400346/java-mission-control-from-jdk-1-8-0-161-frozen-upon-startup-on-mac-os-x 的说明,替换JAR包并重启。
- 使用独立运行的JMC(建议)。↓↓↓↓↓↓↓↓↓
对于JDK 11及更高版本
TIPS
此方式也同样适用于JDK 8
JMC独立出来并开源了,既可以下载独立软件,也可作为Eclipse的插件存在。但不管哪种方式,都需要手动下载。JMC相关地址:
-
Oracle侧有关JMC的官方网站:https://www.oracle.com/technetwork/java/javaseproducts/mission-control/index.html
TIPS:这里面有个视频介绍资料,讲得挺好的,不过使用的版本有点老了
-
Oracle侧有关JMC的官方文档:https://docs.oracle.com/javacomponents/index.html
下载
-
前往 https://jdk.java.net/jmc/ 根据造自己的操作系统下载即可。这里提供百度盘加速下载地址,里面Linux、macOS、Windows都提供了:
链接:https://pan.baidu.com/s/1HXjzNDpzin6fXGrZPyQeWQ 密码:aon2 -
下载完成后,参照 https://www.oracle.com/technetwork/java/javase/jmc-install-6415206.html 的说明安装即可,里面包含了JMC在操作系统、在各种场景下的安装。
使用JMC + JFR
如果想监控的应用所使用的JDK版本 < JDK 11
-
在启动你想监控的应用时,需添加如下参数:
shell-XX:+UnlockCommercialFeatures -XX:+FlightRecorder其中:
- UnlockCommercialFeatures:解锁商业特性
- FlightRecorder:为应用启用或停用JFR(JDK 8u40开始,省略)
-
然后使用JMC连接该应用即可监控该应用
此外,还有其他相关参数可参考:https://docs.oracle.com/javacomponents/jmc-5-5/jfr-command-reference/command-line-options.htm
如果想监控的应用所使用的JDK版本 >= JDK 11
- 被监控的应用无需设置JVM参数,直接启动即可。
- 使用JMC连接该应用即可监控该应用
功能说明
JMX
- 概览:各种概要信息
- MBean浏览器:展示应用被JMX管理的Bean
- 触发器:配置触发规则,当规则满足时,就触发某个操作(在操作一栏配置)
- 系统:查看系统相关信息
- 内存:查看内存相关信息
- 线程:查看线程相关信息
- 诊断命令:可视化使用诊断命令,相当于可视化的jcmd
JFR
- 自动分析结果:JMC自动给出的优化提议
- Java应用程序:展示应用的各种执行情况
- JVM内部:展示JVM层面的执行情况
- 环境:展示操作系统层面的执行情况
- 事件:展示录制期间发生的事件
TIPS
各项指标的含义详见:JMC - 帮助 - JDK Mission Control帮助
插件
- 帮助 - 安装新软件
JMC特点
优点:JMC的主要优点在于对JFR的支持
- JFR在生产环境中对吞吐量的影响一般不会高于1%
- JFR监控过程是可动态的,无需重启
- JFR监控过程对应用完全透明,无需修改应用的代码,也无需安装额外的插件或代理
- JFR提供的数据质量非常高,对监控、排查的参考价值更大
缺点:
- JFR并不完全向后兼容。比如,在JDK 11里面生成的JFR文件,用早期的JMC(例如JMC 5.5)无法打开;
- JMC 7.0.1无法分析堆dump文件(hprof格式),但 官方Wiki 宣称支持分析堆dump文件
参考文档
- Java Mission Control白皮书
- JMC常见问题
- https://dzone.com/articles/using-java-flight-recorder-with-openjdk-11-1
2-13 第三方工具-Memory Analyzer (MAT)
Memory Analyzer (MAT)
TIPS
本文基于MAT 1.10.0编写。该版本最低支持JDK 8
Memory Analyzer又名Memory Analyzer Tool,简称MAT,可以作为独立软件,也可作为Eclipse插件存在。它是一个快速且功能丰富的Java堆内存分析器,可帮助您查找内存泄漏并减少内存消耗。相关地址:
- 官方网站:https://www.eclipse.org/mat/
- 下载地址:https://www.eclipse.org/mat/downloads.php
- Wiki:https://wiki.eclipse.org/MemoryAnalyzer#Getting_a_Heap_Dump
- 使用文档: https://help.eclipse.org/2020-03/index.jsp ,点击左侧
Memory Analyzer菜单
MAT的主要功能:
- 找出内存泄漏的原因
- 找出重复引用的类和jar
- 分析集合的使用
- 分析类加载器
概念
Shallow vs. Retained Heap(浅堆与保留堆)
- 浅堆(Shallow Heap):一个对象消耗的内存。一个对象引用需要32或64 bit(取决于OS体系结构),每个Integer 4个字节,每个Long 8个字节等。根据堆转储格式,这个大小也可能会被调整(例如,对齐为8bit),从而更好地模拟VM的实际消耗量。
- X的保留集(Retained set):当X被垃圾回收时,由GC删除的对象集。同理,如果X没有被回收,那么该集合中的对象都会"保留下来"。
- X的保留堆(Retained heap):X的保留集中的所有对象的浅层大小的总和,即X保持活动的内存。换而言之,Retained heap指的是对象X的保留内存大小,即由于它的存活导致多大的内存没有被回收。
- 前导对象集的保留集(例如特定类的所有对象,或特定类加载器加载的所有类的所有对象,或者仅仅是一堆任意对象):前导对象不可达时,被释放的那些对象。
一般来说,对象的浅堆是对象在堆中的大小,而同一对象的保留大小是在垃圾回收对象时将释放的堆内存量。
Retained set包括这些对象以及只能通过这些对象访问的所有其他对象。保留大小是保留集中包含的所有对象的总堆大小。

TIPS
如图,A和B是GC Roots(例如:方法参数、局部变量,或者调用了wait()、notify()或synchronized()的对象)可以看出:
- E的存在,会导致G无法被回收,因此E的Retained set是E和G;
- C的存在,会导致E、D、F、G、H都无法被回收,因此C的Retined set是C、E、D、F、G、H;
- A和B的存在,会导致C、E、D、F、G、H都无法被回收,因此A和B的Retained set是A、B、C、E、D、F、G、H。
最小保留大小给出了良好的保留估计,其计算速度快于一组对象的确切保留大小。它仅取决于检查集中的对象数,而不取决于堆转储中的对象数。
Dominator Tree(支配树)
MAT提供了对象图的支配树。通过将对象参考图转换为支配树,您可以轻松地识别最大的保留内存块以及对象之间的依赖关系。下面是术语的非正式定义
- X支配Y:如果对象图中从起始(或Root)节点到Y的每条路径都必须经过X,那么就说对象X支配对象Y
- 直接支配者:某个对象路径最短的支配者
支配树是在对象图的基础上建立的,在支配树中,每个节点都是其子节点的直接支配者。因此,基于支配树可以轻松看出对象之间的依赖关系。
支配者树具有以下重要属性:
-
对象从属于X的子树(例如对象被X支配)就是X的Retained set
也就是说,X节点的子树是所有被X支配的节点集合,也就是X的Retained set;
-
如果X是Y的直接支配节点,那么支配X的节点也可以支配Y
-
支配树中的边并不直接对应于对象图中的对象引用

使用
-
独立软件版本:前往 https://www.eclipse.org/mat/downloads.php ,根据自己的操作系统,下载安装即可使用。百度盘加速:
链接:https://pan.baidu.com/s/1HXjzNDpzin6fXGrZPyQeWQ 密码:aon2 -
Eclipse插件版本:在Eclipse插件中心安装即可使用。
欢迎页面

TIPS
主要功能项
- inspector:透视图,用于展示一个对象的详细信息,例如内存地址、加载器名称、包名、对象名称、对象所属的类的父类、对象所属的类的加载器对象、该对象的堆内存大小和保留大小,gc root信息。下半部分展示类的静态属性和值、对象的实例属性和值、对象所属的类的继承结构。
- Heap Dump History:列出最近分析过的文件
- 功能选择栏:从左到右依次是:概览、类直方图、支配树、OQL查询、线程视图、报告相关、详细功能。其中概览就是上图的这个页面,其他则提供了一些更细致的分析能力。总的来说,功能上和VisualVM大同小异,但分析得更加细致。
- 饼图:展示retained size对象占用比例
- Actions:常用的内存分析动作
- Histogram:列出内存中的对象,对象的个数及其大小。点击后生成的报表:
- Class Name : 类名称,java类名
- Objects : 类的对象的数量,这个对象被创建了多少个
- Shallow Heap :一个对象内存的消耗大小,不包含对其他对象的引用
- Retained Heap :是shallow Heap的总和,也就是该对象被GC之后所能回收到内存的总和
- Dominator Tree:列出最大的那些对象,以及他们为什么存活。
- Top Consumers:打印最昂贵的对象,以内和包分组
- Duplicate Classes:检测被多个classloader加载的类
- Histogram:列出内存中的对象,对象的个数及其大小。点击后生成的报表:
- Reports:报表
- Leak Suspects:自动分析内存泄漏的原因,并能直接定位到Class,找到可能导致内存泄露的代码行数。
- Top Components:列出占用超过1%的组件的报告信息
- Step by Step:
- Top Components:分析从属于指定包或者class loader的对象。
个人遇到的问题
启动报如下异常:
java.lang.IllegalStateException: The platform metadata area could not be written: /private/var/folders/xt/xkf315314t38ct3144c_wz4c0000gp/T/AppTranslocation/1B159134-8301-46AC-B119-CB67BA8452F6/d/mat.app/Contents/MacOS/workspace/.metadata. By default the platform writes its content
解决方案:
把mat.app挪到macOS的应用目录下并重启即可。当然也可修改 mat.app/Contents/Eclipse/MemoryAnalyzer.ini 。
参考文档:
- https://www.eclipse.org/forums/index.php/t/1091808/
- https://www.eclipse.org/forums/index.php/t/214452/
- https://github.com/cncgoko/Goko/issues/56
- https://www.vogella.com/tutorials/EclipseMemoryAnalyzer/article.html
- https://community.bonitasoft.com/blog/acquire-heap-dump-mat-memory-analyzer-tool
拓展阅读
参考文章:
2-14 第三方工具- JITWatch
JITWatch
TIPS
[重要程度]:了解即可
JITWatch使用较为复杂,需对编译原理、汇编、机器码有所了解才能玩得比较好。而这些内容,限于本文篇幅,无法展开。
因此,同学们如果一时间掌握不了,也无需担心。本文主要还是为了给大家构建相对完整的工具链知识体系(毕竟JITWatch是在其领域中非常典型的工具)。实际项目中这个领域的调优做得还是比较少的,可以先留个印象,以后汇编、机器码、编译源码等知识积累足够了自然就会比较轻松了。
JITWatch是JIT编译器的日志分析器与可视化工具。可用来检查内联决策、热点方法、字节码以及汇编的各种细节。它经常和HSDIS配合使用。
- GitHub:https://github.com/AdoptOpenJDK/jitwatch
- Wiki:https://github.com/AdoptOpenJDK/jitwatch/wiki/Troubleshooting
安装HSDIS
TIPS
即使不安装HSDIS,也不会影响JITWatch的运行,但是不安装HSDIS的话,就无法用JITWatch查看汇编相关内容了
HSDIS是一个HotSpot虚拟机即时编译代码的反汇编插件,它包含在HotSpot虚拟机的源码当中,在OpenJDK的网站也可以找到单独的源码下载,但并没有提供编译后的程序。
不同操作系统安装方式略有不同,但是总的步骤是一样的:
- 准备好HSDIS文件(从源码编译HSDIS,或下载已编译好的HSDIS文件)
- 将HSDIS文件放置到JDK中指定的目录(详见HSDIS存放目录)
从源码编译
TIPS
这里只讨论了JDK 8和JDK 11。事实上,从JDK 9+开始,安装方式和JDK 11一致。
HSDIS源码地址:
- JDK 11:https://hg.openjdk.java.net/jdk/jdk/archive/jdk-11+7.tar.bz2/src/utils/hsdis
- JDK 8:https://hg.openjdk.java.net/jdk/jdk/archive/jdk8-b116.tar.bz2/hotspot/src/share/tools/hsdis
安装说明
- JDK 11:https://github.com/openjdk/jdk/tree/jdk-11+7/src/utils/hsdis
- JDK 8:https://github.com/openjdk/jdk/tree/jdk8-b116/hotspot/src/share/tools/hsdis
HSDIS存放目录
- JDK 11:
$JAVA_HOME/lib/server/ - JDK 8:存放到
$JAVA_HOME/jre/lib/server/
也可参考 :《 各系统下安装HSDIS 》中的说明编译,里面有各种操作系统如何编译的说明。
下载已编译完成的HSDIS文件
TIPS
不同操作系统、不同JDK版本下的HSDIS文件不能通用,因此,务必搜索和你操作系统、JDK版本都一致的HSDIS文件!!!
-
可在CSDN上搜索各版本的HSDIS文件,不过笔者没有亲测
-
在 GitHub 上有Windows环境下的编译说明,并提供了hsdis的编译后文件
-
适用于macOS JDK 11下的HSDIS文件:
shell链接:https://pan.baidu.com/s/1HXjzNDpzin6fXGrZPyQeWQ 密码:aon2 # 详见tools/hsdis目录
macOS JDK 11下编辑-源码安装HSDIS全过程
shell
1. 下载并解压hsdis
wget https://hg.openjdk.java.net/jdk/jdk/archive/jdk-11+7.tar.bz2/src/utils/hsdis
2. 将目录切换到hsdis目录
cd src/utils/hsdis
3. 在此目录中下载binutils,官方网站:http://sourceware.org/binutils/
wget http://ftp.heanet.ie/mirrors/ftp.gnu.org/gnu/binutils/binutils-2.28.tar.gz
4. 解压binutils
tar -xzf binutils-2.28.tar.gz
5. 编译HSDIS
make BINUTILS=binutils-2.28 ARCH=amd64
6. 将hsdis-amd64.dylib拷贝到$JAVA_HOME/lib/server下
sudo cp build/macosx-amd64/hsdis-amd64.dylib $JAVA_HOME/lib/server/
启动应用
shell
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:+LogCompilation -XX:LogFile=/Users/itmuch.com/logfile.log -XX:+TraceClassLoading -jar xxx.jar
其中:
- UnlockDiagnosticVMOptions:开启诊断信息
- PrintAssembly:输出反汇编内容
- Xcomp:以编译模式启动,这样,无执行足够次数来预热即可触发即时编译
- LogCompilation:打印编译相关信息
- LogFile:指定日志文件
- TraceClassLoading:是否跟踪类的加载
执行完后,将会生成一个 /Users/itmuch.com/logfile.log 文件。里面包括了各种类编辑以及汇编信息
使用JITWatch可视化阅读日志
shell
1. 下载JITWatch源码,并切换到JITWatch的源码根目录(如果不会用Git,也可点击https://github.com/AdoptOpenJDK/jitwatch/archive/master.zip下载源码)
git clone https://github.com/AdoptOpenJDK/jitwatch.git
cd jitwatch
2. 启动JITWatch
mvn clean compile exec:java
3. 点击Open Log,并选择上面的日志文件
4. 点击Config,并配置源码目录及JDK src
5. 点击Start
即可可视化地分析了。
2-15 基于jstatd实现远程连接
基于jstatd实现远程连接
TIPS
- 本文基于JDK 11编写,理论支持JDK 7及更高版本
- 此命令是实验性的,不受支持。
jstatd官方文档
jstatd是一个基于RMI(Remove Method Invocation)的服务程序,它用于监控基于HotSpot的JVM中资源的创建及销毁,并且提供了一个远程接口,从而允许监控工具远程地连接到本地的JVM
一、配置安全策略
TIPS:远程服务器上执行
默认情况下,Java的安全策略比较严格,并不允许jstatd直接启动,因此,需要设置下安全策略。不同的JDK版本配置方式不同,主要是由于JDK 9开始,已经不再有tools.jar导致的。
创建安全策略文件,命名为:jstatd.all.policy,下面是各个版本的配置方式:
JDK 8及更低版本
shell
grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};
当然也可使用绝对路径。
参考文档:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstatd.html
JDK 9及更高版本
shell
grant codebase "jrt:/jdk.jstatd" {
permission java.security.AllPermission;
};
grant codebase "jrt:/jdk.internal.jvmstat" {
permission java.security.AllPermission;
};
参考文档:
二、启动jstatd
TIPS
- 远程服务器上执行
- 执行jstatd命令的用户和你jstatd想连接的应用必须拥有相同的用户凭证
在jstatd.all.policy所在目录,执行如下命令:
shell
jstatd -J-Djava.security.policy=./jstatd.all.policy \n -J-Djava.rmi.server.hostname=www.itmuch.com \n -J-Djava.rmi.server.logCalls=true \n -p 1231
其中:
- java.security.policy:指定上面策略文件的路径,使用相对路径或绝对路径均可
- java.rmi.server.hostname:指定成允许连接的IP地址、主机名或域名
- java.rmi.server.logCalls:打印日志
- -p:用来指定注册端口,默认1099
三、防火墙设置
TIPS:远程服务器上执行
由于jstat使用了RMI,而RMI尽管可指定注册端口,但通信端口是却是rmi server随机生成的------这意味着,每次启动jstatd,使用的端口都不一样。如果能够接受关闭防火墙带来的风险,也可直接关闭掉防火墙。
如果不想关闭防火墙(systemctl stop firewalld.service),可按照如下步骤,配置防火墙规则即可:
shell
# 1. 使用如下命令,获得jstatd监听的端口
➜ netstat -antp | grep jstatd
tcp6 0 0 :::1231 :::* LISTEN 13546/jstatd
tcp6 0 0 :::43201 :::* LISTEN 13546/jstatd
# 2. 开放1231(取决于启动jstatd的-p参数)、43201端口(随机,依赖于netstat -antp | grep jstatd的结果)
➜ firewall-cmd --zone=public --add-port=1231/tcp --permanent
➜ firewall-cmd --zone=public --add-port=43201/tcp --permanent
# 3. 重载防火墙规则
➜ firewall-cmd --reload
# 4. 查看当前开放的端口,确认端口已成功开放
➜ firewall-cmd --zone=public --list-ports
另外,也可参考《 rmi穿越防火墙 》的描述,自己编写代码,实现穿透防火墙。大致思路是自定义一个RMISocketFactory并指定固定的通信端口,然后人工设置一个固定的端口(比如2098),再为防火墙配置开放该端口即可。不过这种方式需要侵入源码,个人并不建议使用。
四、本地机器配置连接远程服务器
VisualVM

jps
shell
jps rmi://www.itmuch.com:1231
参考文档
- jstatd,VisualVM使用和报错解决:Could not create remote object--java.security.AccessControlException
- jvisualvm connect to remote jstatd not showing applications
- 使用jvisualVm监控本地和远程的jvm
- jvisualvm连接远程应用终于成功,附踩大坑记录!!(一:jstatd方式)
2-16 基于JMX实现远程连接
基于JMX实现远程连接
TIPS
本文基于JDK 11编写,理论支持JDK 7及更高版本
本文探讨如何基于JMX实现连接远程服务器上的进程。
匿名访问
在远程服务器上操作:
一、启动远程应用
shell
java -Djava.rmi.server.hostname=www.itmuch.com \n -Dcom.sun.management.jmxremote.port=1232 \n -Dcom.sun.management.jmxremote.rmi.port=1240 \n -Dcom.sun.management.jmxremote.authenticate=false \n -Dcom.sun.management.jmxremote.ssl=false \n -jar xxx.jar
其中:
- java.rmi.server.hostname:指定主机名,可填IP、主机名或域名
- com.sun.management.jmxremote.port:指定JMX监听端口,默认1099
- com.sun.management.jmxremote.rmi.port:指定RMI的通信端口,默认随机分配
- com.sun.management.jmxremote.authenticate:是否启用认证访问,默认true
- com.sun.management.jmxremote.ssl:是否启用ssl,默认true
二、防火墙设置
JMX Server除使用指定的监听端口号外,还需用使用RMI通信,因此,需将JMX监听端口、RMI通信端口都开放。
TIPS
如果未设置com.sun.management.jmxremote.rmi.port,那么RMI通信端口是随机的。可使用如下命令查询指定进程使用的端口。
shell# 使用如下命令,查询该进程使用的端口 ➜ netstat -antp|grep 50194 tcp6 0 0 :::8080 :::* LISTEN 50194/java tcp6 0 0 :::1240 :::* LISTEN 50194/java tcp6 0 0 :::1232 :::* LISTEN 50194/java tcp6 0 0 :::51992 :::* LISTEN 50194/java tcp6 0 0 116.255.183.43:37206 116.255.183.43:27017 ESTABLISHED 50194/java
参考如下步骤,配置防火墙规则,开放相应端口。当然,如果能接受关闭防火墙带来的风险,也可直接关闭掉防火墙。
shell
# 1. 使用如下命令,获得想要查看的应用的进程号
➜ jps
50513 Jps
50194 backend-0.0.1-SNAPSHOT.jar
# 3. 使用如下命令,开放指定端口
firewall-cmd --zone=public --add-port=1232/tcp --permanent
firewall-cmd --zone=public --add-port=1240/tcp --permanent
....
# 4. 重载防火墙规则
firewall-cmd --reload
# 5. 查看当前开放的端口,确认端口已成功开放
firewall-cmd --zone=public --list-ports
三、远程连接,以VisualVM为例
本机启动VisualVM,配置如下:
- File - Add JMX Connection,填入类似如图的信息:

即可连接成功。
开启认证
上面,应用在启动的时候,使用com.sun.management.jmxremote.authenticate=false,关闭了JMX的登录认证。下面来探讨如何使用登录认证连接JMX。
可按如下步骤操作:
一、准备账号文件,命名为jmxremote.access,内容如下
shell
admin readwrite
其中:
- admin是账号,多个账号换行继续写即可。
- readwrite指权限,readwrite表示可读可写;readonly表示只读。
二、准备密码文件,命名为jmxremote.password,内容如下
shell
admin admin123
其中,admin是账号,admin123是密码,多个账号换行继续写即可。
三、设置jmxremote.access、jmxremote.password的文件权限
shell
chmod 600 jmxremote.access
chmod 600 jmxremote.password
四、启动应用
shell
java -Djava.rmi.server.hostname=www.itmuch.com \n -Dcom.sun.management.jmxremote.port=1232 \n -Dcom.sun.management.jmxremote.rmi.port=1240 \n -Dcom.sun.management.jmxremote.authenticate=true \n -Dcom.sun.management.jmxremote.access.file=./jmxremote.access \n -Dcom.sun.management.jmxremote.password.file=./jmxremote.password \n -Dcom.sun.management.jmxremote.ssl=false \n -jar xxx.jar
五、连接时,指定上面的账号密码即可:

开启SSL
上面使用了com.sun.management.jmxremote.ssl=false关闭了SSL,下面来探讨如何开启SSL,从而提升通信的安全性。
前导知识
先了解一下Java客户端程序在创建SSL连接的一些相关的事情:
-
Java client程序在做SSL连接的时候,会拉取server的证书,利用truststore去验证这个证书,如果truststore不存在、证书过期抑或证书不是由可信CA签发的,就意味着服务端不被信任,不允许连接。
-
如果在程序启动时没有特别指定使用哪个truststore,就会使用
$JAVA_HOME/jre/lib/security/cacerts。也可通过System Propertyjavax.net.ssl.trustStore指定truststore,指定了truststore时,会使用指定的truststore + cacerts来验证。 -
cacerts存放了JDK信任的CA证书(含有public key),它里面预先已经存放了已知的权威CA证书。可通过如下命令查看。
shellkeytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts以上过程被称为server authentication,也就是说client验证server是否可信,server authentication是最常见的,https就是这种模式。
不过在用SSL连接JMX的时候,不仅要做server authentication,还要做client authentication,即server验证client是否可信。原理和上面一样,只不过变成server用自己的truststore里验证client的证书是否可信。
理解这一点之后,就可以制作keystore以及truststore了。
制作keystore和truststore
下面,以visualvm为例,讲解如何实现基于SSL的连接。需要制作visualvm(本机)和java app(server端)的keystore和truststore。
- 生成visualvm的keystore,导出cert,把cert导入到java-app的truststore里
- 生成java-app的keystore,导出cert,把cert导入到visualvm的truststore里
TIPS
这里只是以visualvm为例讲解,JConsole等过程也是一样的。
具体命令如下:
TIPS
下面所有命令中,以<>包裹的都是变量,需要替换成你希望的值
-
生成visualvm的keystore
shellkeytool -genkeypair \n -alias visualvm \n -keyalg RSA \n -validity 365 \n -storetype pkcs12 \n -keystore visualvm.keystore \n -storepass <visualvm keystore的密码> \n -keypass <同visualvm keystore的密码> \n -dname "CN=<姓名>, OU=<组织下属单位>, O=<组织名称>, L=<城市>, S=<省份>, C=<国家2字母>"示例:
shellkeytool -genkeypair \n -alias visualvm \n -keyalg RSA \n -validity 365 \n -storetype pkcs12 \n -keystore visualvm.keystore \n -storepass visualvm-key \n -keypass visualvm-key \n -dname "CN=damu, OU=itmuch, O=itmuch.com, L=NanJing, S=JiangSu, C=CN"如果出现类似
keytool 错误: java.lang.Exception: 未生成密钥对, 别名 <visualvm> 已经存在的异常,只需删除visualvm.keystore文件,然后重新执行该命令即可。 -
导出visualvm的cert
shellkeytool -exportcert \n -alias visualvm \n -storetype pkcs12 \n -keystore visualvm.keystore \n -file visualvm.cer \n -storepass <visualvm keystore的密码>示例:
shellkeytool -exportcert \n -alias visualvm \n -storetype pkcs12 \n -keystore visualvm.keystore \n -file visualvm.cer \n -storepass visualvm-key -
把visualvm的cert导入到java-app的truststore里,实际上就是生成了一个truststore
shellkeytool -importcert \n -alias visualvm \n -file visualvm.cer \n -keystore java-app.truststore \n -storepass <java-app truststore的密码> \n -noprompt示例:
shellkeytool -importcert \n -alias visualvm \n -file visualvm.cer \n -keystore java-app.truststore \n -storepass java-app-trust \n -noprompt -
生成java-app的keystore
shellkeytool -genkeypair \n -alias java-app \n -keyalg RSA \n -validity 365 \n -storetype pkcs12 \n -keystore java-app.keystore \n -storepass <java-app keystore的密码> \n -keypass <同java-app keystore的密码> \n -dname "CN=<姓名>, OU=<组织下属单位>, O=<组织名称>, L=<城市>, S=<省份>, C=<国家2字母>"示例:
shellkeytool -genkeypair \n -alias java-app \n -keyalg RSA \n -validity 365 \n -storetype pkcs12 \n -keystore java-app.keystore \n -storepass java-app-key \n -keypass java-app-key \n -dname "CN=damu, OU=itmuch, O=itmuch.com, L=NanJing, S=JiangSu, C=CN" -
导出java-app的cert
shellkeytool -exportcert \n -alias java-app \n -storetype pkcs12 \n -keystore java-app.keystore \n -file java-app.cer \n -storepass <java-app keystore的密码>示例:
shellkeytool -exportcert \n -alias java-app \n -storetype pkcs12 \n -keystore java-app.keystore \n -file java-app.cer \n -storepass java-app-key -
把java-app的cert导入到visualvm的truststore里
shellkeytool -importcert \n -alias java-app \n -file java-app.cer \n -keystore visualvm.truststore \n -storepass <visualvm truststore的密码> \n -noprompt示例:
shellkeytool -importcert \n -alias java-app \n -file java-app.cer \n -keystore visualvm.truststore \n -storepass visualvm-trust \n -noprompt
最终可获得如下几个文件:
- visualvm.keystore
- visualvm.truststore
- visualvm.cer
- java-app.keystore
- java-app.truststore
- java-app.cer
启动远程Java应用
shell
java -Djava.rmi.server.hostname=www.itmuch.com \n -Dcom.sun.management.jmxremote.port=1232 \n -Dcom.sun.management.jmxremote.rmi.port=1240 \n -Dcom.sun.management.jmxremote.authenticate=false \n -Djavax.net.ssl.keyStore=<path to java-app.keystore> \n -Djavax.net.ssl.keyStorePassword=<java-app.keystore的密码> \n -Djavax.net.ssl.trustStore=<path to java-app.truststore> \n -Djavax.net.ssl.trustStorePassword=<java-app.truststore的密码> \n -jar xxx.jar
示例:
shell
java -Djava.rmi.server.hostname=www.itmuch.com \n -Dcom.sun.management.jmxremote.port=1232 \n -Dcom.sun.management.jmxremote.rmi.port=1240 \n -Dcom.sun.management.jmxremote.authenticate=false \n -Djavax.net.ssl.keyStore=/root/test/java-app.keystore \n -Djavax.net.ssl.keyStorePassword=java-app-key \n -Djavax.net.ssl.trustStore=/root/test/java-app.truststore \n -Djavax.net.ssl.trustStorePassword=java-app-trust \n -jar xxx.jar
TIPS
这里简单起见,没有开启用户认证,你也可以同时开启用户认证与SSL。
启动VisualVM
shell
jvisualvm -J-Djavax.net.ssl.keyStore=<path to visualvm.keystore> \n -J-Djavax.net.ssl.keyStorePassword=<visualvm.keystore的密码> \n -J-Djavax.net.ssl.trustStore=<path to visualvm.truststore> \n -J-Djavax.net.ssl.trustStorePassword=<visualvm.truststore的密码>
示例:
shell
jvisualvm -J-Djavax.net.ssl.keyStore=/Users/reno/Documents/visualvm.keystore \n -J-Djavax.net.ssl.keyStorePassword=visualvm-key \n -J-Djavax.net.ssl.trustStore=/Users/reno/Documents/visualvm.truststore \n -J-Djavax.net.ssl.trustStorePassword=visualvm-trust
TIPS
对于JDK 8,JDK内置了VisualVM,使用如上命令即可;
对于JDK 11,VisualVM是独立分发的,根本没有jvisualvm这样的命令。此时,应找到visualvm的启动文件。对于macOS,启动文件是/Applications/VisualVM.app/Contents/MacOS/visualvm。因此,只需将上面的命令改为如下即可:
shell/Applications/VisualVM.app/Contents/MacOS/visualvm \n> -J-Djavax.net.ssl.keyStore=/Users/reno/Documents/visualvm.keystore \n> -J-Djavax.net.ssl.keyStorePassword=visualvm-key \n> -J-Djavax.net.ssl.trustStore=/Users/reno/Documents/visualvm.truststore \n> -J-Djavax.net.ssl.trustStorePassword=visualvm-trust
其他操作系统类似,找到VisualVM的启动文件,并在其后跟上参数即可。
参考文档
- VisualVM官方文档
- VisualVM: Monitoring Remote JVM Over SSH (JMX Or Not)
- 利用VisualVm和JMX远程监控Java进程
- Java_jvisualvm使用JMX连接远程机器(实践)
- VisualVM通过密码JMX远程连接JVM
- Monitoring and Management Using JMX Technology
2-17 基于SSH实现远程连接
基于SSH实现远程连接
TIPS
本文基于JDK 11编写,理论支持JDK 7及更高版本
jstatd & JMX存在的问题:
- jstatd虽然连接相对方便,但是没有考虑过网络安全相关的问题(对于公网环境,直接开放端口可能存在安全风险)
- JMX虽然可以开启用户认证以及ssl,但是要做一堆的配置
使用本文的玩法,可以让jstatd更安全;让JMX的安全不那么麻烦。
使用SSH连接到JMX或jstatd的本质,是利用SSH实现了一个SOCKS代理,添加代理后,请求过程:本地 -> 连接SSH代理 -> 转发到你想请求的地址。而SSH是安全的,所以这种方式也比较安全。
一、启动远程应用
远程服务器执行:
shell
java -Djava.rmi.server.hostname=www.itmuch.com \n -Dcom.sun.management.jmxremote.port=1235 \n -Dcom.sun.management.jmxremote.rmi.port=1250 \n -Dcom.sun.management.jmxremote.ssl=false \n -Dcom.sun.management.jmxremote.authenticate=false \n -jar xxx.jar
注意,这里的hostname写localhost即可。当然,你还可以去开启认证、开启ssl。
二、SSH端口转发
在本机执行如下命令,SSH实现端口转发
shell
ssh -v -D 9696 root@www.itmuch.com
其中:
- -v:verbose模式,打印详情
- -D:动态应用程序级端口转发,格式:
-D [bind_address:]端口。将会分配一个Sock并监听本地端口。与此端口建立连接后,该链接将通过安全通道转发,然后使用应用协议确定从远程计算机连接到的位置 。
TIPS
- SSH命令的帮助文档:https://www.ssh.com/ssh/command/
- 对于Windows机器,可利用SSH远程工具提供的代理功能。例如SecureCRT、XShell、PuTTY等,都支持端口转发。本文无法一一穷举,读者可自行摸索。
- 例如:在SecureCRT上配置代理,详见 https://www.vandyke.com/support/tips/socksproxy.html
三、配置代理
以VisualVM为例,配置成如下即可:

四、连接应用
File - Add JMX Connection,填入类似如图的信息即可:

端口需和上面com.sun.management.jmxremote.port=1235指定的端口一致。
参考文档
https://stackoverflow.com/questions/1609961/visualvm-over-ssh
http://www.nexusy.com/2014/02/20/ssh-jvisualvm/
https://dzone.com/articles/visualvm-monitoring-remote-jvm
http://issamben.com/how-to-monitor-remote-jvm-over-ssh/
https://segmentfault.com/a/1190000016661877?spm=a2c4e.10696291.0.0.503b19a4VLYYv7