性能指标
停顿时间(响应时间)
提交请求和返回响应之间使用的时间,一般比较关注平均响应时间
常用操作的响应时间列表:
操作 | 响应时间 |
打开一个站点 | 几秒 |
数据库查询一条记录(有索引) | 十几毫秒 |
机械磁盘一次寻址定位 | 4毫秒 |
从机械磁盘顺序读取1M数据 | 2毫秒 |
从SSD磁盘顺序读取1M数据 |
0.3毫秒 |
从远程分布式缓存Redis读取一个数据 | 0.5毫秒 |
从内存读取1M数据 | 十几微妙 |
Java程序本地方法调用 | 几微秒 |
网络传输2KB数据 | 1微秒 |
吞吐量
对单位时间内完成的工作量(请求)的量度
并发数
同一时刻,对服务器有实际交互的请求数
内存占用
java堆区所占的内存大小
JVM监控及诊断工具-命令行
首先盘点一下哪些资源便于问题的排查:
- GC 日志,能够反映每次 GC 的具体状况,可根据这些信息调整一些参数及容量;
- 问题发生点的堆快照,能够在线下找到具体内存泄漏的原因;
- 问题发生点的堆栈信息,能够定位到当前正在运行的业务,以及一些死锁问题;
- 操作系统监控,比如 CPU 资源、内存、网络、I/O 等,能够看到问题发生前后整个操作系统的资源状况;
- 服务监控,比如服务的访问量、响应时间等,可以评估故障堆服务的影响面,或者找到一些突增的流量来源;
- JVM 各个区的内存变化、GC 变化、耗时等监控,能够帮我们了解到 JVM 在整个故障周期的时间跨度上,到底发生了什么。
JPS(Java Process Status)
操作系统提供一个工具叫做 ps,用于显示进程状态(Process Status)。Java也 提供了类似的命令行工具,叫做 JPS,用于显示指定系统内所有的HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。
说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。
需要注意的是,JPS 展示的是当前用户可看见的 Java 进程,如果看不见某些进程可能需要 sudo、su 之类的命令来切换权限。如果某 Java 进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该 Java 进程。
基本用法:
usage: jps [-help] jps [-q] [-mlvV] [<hostid>] Definitions: <hostid>: <hostname>[:<port>]
options参数
-q:仅仅显示LVMID(local virtual machine id),即本地虚拟机唯一id,不显示主类的名称等
-l:输出应用程序主类的全类名,如果进程执行的是jar包,则输出jar完整路径
-m:输出虚拟机进程启动时传递给主类main()的参数
-v:列出虚拟机进程启动时的JVM参数。比如:-Xms20m -Xmx50m是启动程序指定的JVM参数。
<hostid>
:部分是远程主机的标识符,需要远程主机启动 jstatd
服务器支持。可以看到,格式为 <hostname>[:<port>]
,不能用 IP,示例:jps -v sample.com:1099
。
注意:以上参数可以综合使用。
补充:如果某Java进程关闭了默认开启的UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及下面介绍的jstat)将无法探知该Java进程。
hostid参数
RMI注册表中注册的主机名
如果想要远程监控主机上的java程序,需要安装jstatd。
对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络的访问,尽管这种技术容易受到IP地址欺诈攻击。
如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行jstatd服务器,而是在本地使用jstat和jps工具。
Jstat(JVM Statistics Monitoring Tool)
用于监视虚拟机各种运行状态信息的命令行工具,主要是内存和 GC 相关的信息。。它可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、JIT编译等运行数据。
在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题已经内存泄漏问题。
查看命令相关参数
jstat -h 或jstat -help
基本用法:
Usage: jstat -help|-options jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]] Definitions: <option> 可用的选项,查看详情请使用 -options <vmid> 虚拟机标识符,格式:<lvmid>[@<hostname>[:<port>]] <lines> 标题行间隔的频率. <interval> 采样周期,<n>["ms"|"s"],默认单位是毫秒 "ms" <count> 采用总次数 -J<flag> 传给jstat底层JVM的 <flag> 参数
option参数
选项option可以由以下值构成。
- 类装载相关:
- -class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
- 垃圾回收相关:
- -gc:显示与GC相关的堆内存信息。包括Eden去、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息,用法:
jstat -gc -h 10 -t 864 1s 20
。 - -gccapacity:各个内存池分代空间的容量。
- -gcutil:GC 相关区域的使用率(utilization)统计。
- -gccause:看上次 GC、本次 GC(如果正在 GC 中)的原因,其他输出和
-gcutil
选项一致 - -gcnew:年轻代的统计信息(New = Young = Eden + S0 + S1)。
- -gcnewcapacity:年轻代空间大小统计。
- -gcold:老年代和元数据区的行为统计。
- -gcoldcapacity:old 空间大小统计。
- -gcmetacapacity:meta 区大小统计。
- -gc:显示与GC相关的堆内存信息。包括Eden去、两个Survivor区、老年代、永久代等的容量、已用空间、GC时间合计等信息,用法:
JIT相关:
- -compiler:显示JIT编译器编译过的方法、耗时等信息
- -printcompilation:打印 JVM 编译统计信息。
interval参数
用于指定输出统计数据的周期,单位为毫秒。即:查询间隔。每隔多久打印一次信息。
count参数
用于指定查询的总次数
-t参数
可以在输出信息前加上一个Timestamp列,显示程序的运行时间。单位:秒
-h参数
可以在周期性数据输出时,输出多少行数据后输出一个表头信息。
经验:
我们可以比较Java进程的启动时间以及总GC时间(GCT列),或者两次测量的间隔时间以及总GC时间的增量,来得出GC时间占运行时间的比例。如果该比例超过20%,则说明目前堆的压力较大;如果该比例超过90%,则说明堆里几乎没有可用空间,随时都可能抛出OOM异常。
jstat还可以用来判断是否出现内存泄漏。第一步,在长时间运行的Java程序中,我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中OU列(即已占用的老年代内存)的最小值。第二步,我们每隔一段较长时间重复一次上述操作,来获得多组OU最小值。如果这些值呈上涨趋势,则说明该Java程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。
举例:
jstat -class -t -h3 25004 1000 10
显示结果:
Timestamp Loaded Bytes Unloaded Bytes Time 29.6 620 1248.9 0 0.0 0.13 30.6 620 1248.9 0 0.0 0.13 31.6 620 1248.9 0 0.0 0.13 Timestamp Loaded Bytes Unloaded Bytes Time 32.6 620 1248.9 0 0.0 0.13 33.6 620 1248.9 0 0.0 0.13 34.6 620 1248.9 0 0.0 0.13 Timestamp Loaded Bytes Unloaded Bytes Time 35.6 620 1248.9 0 0.0 0.13 36.6 620 1248.9 0 0.0 0.13 37.6 620 1248.9 0 0.0 0.13 Timestamp Loaded Bytes Unloaded Bytes Time 38.6 620 1248.9 0 0.0 0.13
加载了620个类,占1248.9 byte,卸载了0个类,占用0 byte,花费的时间 0.13。每隔1000毫秒打印一次信息,一直运行了38.6秒。每隔3行打印一次表头。总共打印了10次。
新建demo类
public class GCTest { public static void main(String[] args) { List<byte[]> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { byte[] arr = new byte[1024 * 100]; list.add(arr); try { Thread.sleep(120); } catch (InterruptedException e) { e.printStackTrace(); } } } }
增加JVM参数:
-Xms60m -Xmx60m -XX:SurvivorRatio=8
运行以后,执行以下命令
jstat -gc 23424 1000 10
结果显示:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 2048.0 2048.0 0.0 0.0 16384.0 9477.3 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 10277.4 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 11177.6 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 11977.7 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 12777.8 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 13677.9 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 14478.1 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 15378.2 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 16178.3 40960.0 29860.9 4864.0 3782.0 512.0 417.9 2 0.007 1 0.008 0.016 2048.0 2048.0 0.0 0.0 16384.0 5714.3 40960.0 40906.7 4864.0 3782.1 512.0 417.9 2 0.007 2 0.019 0.026
S0C:0 号存活区的当前容量(capacity),单位 kB。
S1C:1 号存活区的当前容量,单位 kB。
S0U:0 号存活区的使用量(utilization),单位 kB。
S1U:1 号存活区的使用量,单位 kB。
EC:Eden区,新生代的当前容量,单位 kB。
EU:Eden区,新生代的使用量,单位 kB。
OC:Old 区,老年代的当前容量,单位 kB。
OU:Old 区,老年代的使用量,单位 kB。
MC:元数据区的容量,单位 kB。
MU:元数据区的使用量,单位 kB。
CCSC:压缩的 class 空间容量,单位 kB。
CCSU:压缩的 class 空间使用量,单位 kB。
YGC:从应用程序启动到采样时young GC的次数
YGCT:从应用程序启动到采样时young GC消耗的时间(秒)
FGC:从应用程序启动到采样时Full GC的次数
FGCT:从应用程序启动到采样时Full GC消耗的时间(秒)
GCT:从应用程序启动到采样时GC消耗的总时间(秒)
最重要的信息是 GC 的次数和总消耗时间,其次是老年代的使用量。
Jinfo(Configuration Info for Java)
查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。
在很多情况下,Java应用程序不会指定所有的Java虚拟机参数。而此时,开发人员可能不知道某一个具体的Java虚拟机参数的默认值。在这种情况下,可能需要通过查找文档获取某个参数的默认值。这个查找过程可能是非常艰难的。但有了jinfo工具,开发人员可以很方便地找到Java虚拟机参数的当前值。
基本语法:
jinfo [options] pid
options参数
- -no option:输出全部的参数和系统属性
- -flag name:输出对应名称的参数
- -flag[+-] name:开启或关闭对应名称的参数,只有被标记为manageable的参数才可以被动态修改
- -flag name = value:设定对应名称的参数
- -flags:输出全部的参数
- -sysprops:输出系统属性
jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值,甚至可以在运行时修改部分参数,并使之立即生效。但是,并不是所有参数都支持动态修改。参数只有被标记为manageable的flag可以被实时修改。其实,这个修改能力是极其有限的。
可以使用以下命令查看被标记为manageable的参数
java -XX:+PrintFlagsFinal -version | grep manageable
java -XX:+PrintFlagsInitial :查看所有JVM参数启动的初始值
java -XX:+PrintFlagsFinal :查看所有JVM参数的最终值
java -XX:+PrintCommandLineFlags:查看哪些已经被用户或者JVM设置过的详细的XX参数的名称和值。
Jmap(JVM Memory Map)
获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标Java进程的内存相关信息,包括Java堆各区域的使用情况、堆中对象的统计信息、类加载信息等。
查看 jmap 帮助信息:
jmap -help
基本用法
Usage: jmap [option] <pid> (连接到本地进程) jmap [option] <executable <core> (连接到 core file) jmap [option] [server_id@]<remote-IP-hostname> (连接到远程 debug 服务) where <option> is one of: <none> 等同于 Solaris 的 pmap 命令 -heap 打印 Java 堆内存汇总信息 -histo[:live] 打印 Java 堆内存对象的直方图统计信息 如果指定了 "live" 选项则只统计存活对象,强制触发一次 GC -clstats 打印 class loader 统计信息 -finalizerinfo 打印等待 finalization 的对象信息 -dump:<dump-options> 将堆内存 dump 为 hprof 二进制格式 支持的 dump-options: live 只 dump 存活对象,不指定则导出全部。 format=b 二进制格式(binary format) file=<file> 导出文件的路径 示例:jmap -dump:live,format=b,file=heap.bin <pid> -F 强制导出,若 jmap 被 hang 住不响应,可断开后使用此选项。 其中 "live" 选项不支持强制导出。 -h | -help to print this help message -J<flag> to pass <flag> directly to the runtime system
option参数
- -dump:生成Java堆转储快照:dump文件。特别的:-dump:live 只保存堆中的存活对象
- -clstats:打印被加载类的信息。
- -finalizeinfo:显示在F-Queue中等待Finalizer线程执行finalize方法的对象,仅linux/solaris平台有效
- -heap:输出整个堆空间的详细信息,包括GC的使用、堆配置信息、以及内存的使用信息等
- -histo:输出堆空间中对象的统计信息,包括类、实例数量和合计容量。特别的:-history:live只统计堆中存活对象
- -permstat:以ClassLoader为统计口径输出永久代的内存状态信息,仅linux/solaris平台有效
- -F:当虚拟机进程对-dump选项没有任何响应时,强制执行生成dump文件,仅linux/solaris平台有效
- -h | -help:jmap工具使用的帮助命令
- -J<flag>:传递参数给jmap启动的JVM
功能
1.导出内存映像文件
- 手动方式
- jmap -dump:format=b,file=<filename.hprof> <pid>
- jmap -dump:live,format=b,file=<filename.hprof> <pid>
- 自动方式
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:+HeapDumpAfterFullGC
- -XX:HeapDumpPath=<filename.hprof>
一般来说,使用jmap指令生成dump文件的操作算得上是最常用的jmap命令之一,将堆中所有存活对象导出至一个文件之中。
Heap Dump又叫做堆存储文件,指一个Java进程在某个时间点的内存快照。Heap Dump在触发内存快照的时候会保存此刻的信息如下:
- All Objects:Class,field,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后留下的对象信息。针对的是自动方式。
- 由于生成dump文件比较耗时,所以需要等待一段时间,尤其是大内存镜像生成dump文件,耗费的时间会更长。
2.显示堆内存相关信息
jmap -heap <pid>:显示Java堆详细信息
jmap -histo[:live] <pid>:显示堆中对象的统计信息
小结
由于jmap将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由jmap导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。例如:在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么:live选项将无法探知到这些对象。另外,如果某个线程长时间无法跑到安全点,jmap将一直等下去。与前面讲的jstat则不同,垃圾回收器会主动将jstat所需要的摘要数据保存至固定位置之中,而jstat只需直接读取即可。
jmap
(以及jinfo
、jstack
和jcmd
)依赖于 Java 虚拟机的Attach API,因此只能监控本地 Java 进程。一旦开启 Java 虚拟机参数DisableAttachMechanism(即使用参数-XX:+DisableAttachMechanism),基于 Attach API 的命令将无法执行。反过来说,如果你不想被其他进程监控,那么你需要开启该参数。
Jhat(JVM Heap Analysis Tool)
Sun JDK提供的jhat命令与jmap命令搭配使用,用于分析jmap生成的heap dump文件。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。
使用jhat命令,会启动一个http服务,端口是7000,即http://localhost:7000,就可以在浏览器里分析。jhat命令在JDK9、JDK10中已经被删除,官方建议用visualVM代替。
基本用法
jhat [option] [dumpfile]
option参数
- -stack false | true:关闭|打开对象分配调用栈跟踪。如果分配位置信息在堆转储中不可用,则必须将此标志设置为 false,默认值为 true。
- -refs false | true :关闭|打开对象引用跟踪 ,默认值为 true。默认情况下,返回的指针是指向其他特定对象的对象,如反向链接或输入引用(referrers or incoming references),会统计/计算堆中的所有对象。
- -port number:设置jhat HTTP Server 的端口号,默认是7000
- -exclude file:执行对象查询时需要排除的数据成员列表文件。例如,如果文件列列出了 java.lang.String.value,那么当从某个特定对象 Object o 计算可达的对象列表时,引用路径涉及 java.lang.String.value 的都会被排除。
- -baseline file:指定一个基准堆转储。在两个 heap dumps 中有相同 object ID 的对象会被标记为不是新的。其他对象被标记为新的(new)。在比较两个不同的堆转储时很有用。
- -debug int:设置debug级别,0 表示不输出调试信息,值越大则表示输出更详细的 debug 信息。
- -version:启动后显示版本信息就退出
- -h,即-help。显示帮助信息并退出. 同 -h 。
- -J<flag>:因为 jhat 命令实际上会启动一个 JVM 来执行,通过 -J 可以在启动 JVM 时传入一些启动参数,比如 -J -Xmx512m则指定运行 jhat 的 Java 虚拟机使用的最大堆内存为 512 MB。如果需要使用多个 JVM 启动参数,则传入多个-Jxxxxx。
dumpfile参数
要查看的二进制 Java 堆转储文件(Java binary heap dump file)。如果某个转储文件中包含了多份 heap dumps,可在文件名之后加上 #<number> 的方式指定解析哪一个 dump,如:myfile.hprof#3。
jhat 示例
使用 jmap 工具转储堆内存、可以使用如下方式:
jmap -dump:file=DumpFileName.txt,format=b <pid>
然后分析时使用 jhat 命令,如下所示:
jhat -J-Xmx1024m D:/javaDump.hprof
使用参数 -J-Xmx1024m 是因为默认 JVM 的堆内存可能不足以加载整个 dump 文件,可根据需要进行调整。然后我们可以根据提示知道端口号是 7000,接着使用浏览器访问 http://localhost:7000/ 即可看到相关分析结果。
详细说明
jhat 命令支持预先设计的查询,比如显示某个类的所有实例。还支持 对象查询语言(OQL,Object Query Language),OQL 有点类似 SQL,专门用来查询堆转储。OQL 相关的帮助信息可以在 jhat 命令所提供的服务器页面最底部。如果使用默认端口,则 OQL 帮助信息页面为:http://localhost:7000/oqlhelp/
Java 生成堆转储的方式有多种:
- 使用 jmap -dump 选项可以在 JVM 运行时获取 heap dump(可以参考上面的示例)。
- 使用 jconsole 选项通过 HotSpotDiagnosticMXBean 从运行时获得堆转储。
- 在虚拟机启动时如果指定了 -XX:+HeapDumpOnOutOfMemoryError 选项,则抛出 OutOfMemoryError 时,会自动执行堆转储。
- 使用 hprof 命令。
jstack(JVM Stack Trace)
用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。
生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用jstack显示各个线程调用的堆栈情况。
看看帮助信息:
jstack -help
Usage: jstack [-l] <pid> (to connect to running process) jstack -F [-m] [-l] <pid> (to connect to a hung process) jstack [-m] [-l] <executable> <core> (to connect to a core file) jstack [-m] [-l] [server_id@]<remote server IP or hostname> (to connect to a remote debug server) Options: -F to force a thread dump. Use when jstack <pid> does not respond (process is hung) -m to print both java and native frames (mixed mode) -l long listing. Prints additional information about locks -h or -help to print this help message
选项说明:
-F
:强制执行 Thread Dump,可在 Java 进程卡死(hung 住)时使用,此选项可能需要系统权限。-m
:混合模式(mixed mode),将 Java 帧和 native 帧一起输出,此选项可能需要系统权限。-l
:长列表模式,将线程相关的 locks 信息一起输出,比如持有的锁,等待的锁。
常用的选项是 -l
,示例用法。
jstack 4524 jstack -l 4524
在 Linux 和 macOS 上,jstack pid
的效果跟 kill -3 pid
相同。
在thread dump中,有以下几种状态
- 死锁,Deadlock(重点关注)
- 等待资源,waiting on condition(重点关注)
- 等待获取监视器,waiting on monitor entry(重点关注)
- 阻塞,blocked(重点关注)
- 执行中,Runnable
- 暂停,Suspended
- 对象等待中,Object.wait()或 TIMED_WAITING
- 停止,Parked
死锁demo
代码:
public class ThreadDeadLock { public static void main(String[] args) { StringBuilder s1 = new StringBuilder(); StringBuilder s2 = new StringBuilder(); new Thread(() -> a(s1, s2)).start(); new Thread(() -> b(s1, s2)).start(); } public static void a(StringBuilder s1, StringBuilder s2) { synchronized (s1) { s1.append("a"); s2.append("1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2) { s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } public static void b(StringBuilder s1, StringBuilder s2) { synchronized (s2) { s1.append("c"); s2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1) { s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }
执行以下指令
jstack 31392
结果显示
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.66-b18 mixed mode): "DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000000003062800 nid=0x7390 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Thread-1" #12 prio=5 os_prio=0 tid=0x000000001ef6a000 nid=0x4f94 waiting for monitor entry [0x000000001f7ff000] java.lang.Thread.State: BLOCKED (on object monitor) at com.fhj.jvm.ThreadDeadLock.b(ThreadDeadLock.java:41) - waiting to lock <0x000000076c07eb80> (a java.lang.StringBuilder) - locked <0x000000076c07ebc8> (a java.lang.StringBuilder) at com.fhj.jvm.ThreadDeadLock.lambda$main$1(ThreadDeadLock.java:11) at com.fhj.jvm.ThreadDeadLock$$Lambda$2/455896770.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Thread-0" #11 prio=5 os_prio=0 tid=0x000000001ef90000 nid=0x19fc waiting for monitor entry [0x000000001f6ff000] java.lang.Thread.State: BLOCKED (on object monitor) at com.fhj.jvm.ThreadDeadLock.a(ThreadDeadLock.java:24) - waiting to lock <0x000000076c07ebc8> (a java.lang.StringBuilder) - locked <0x000000076c07eb80> (a java.lang.StringBuilder) at com.fhj.jvm.ThreadDeadLock.lambda$main$0(ThreadDeadLock.java:10) at com.fhj.jvm.ThreadDeadLock$$Lambda$1/2094548358.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000001e538800 nid=0x213c runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001e3f8000 nid=0x6d3c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001e3f6000 nid=0x355c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001e39b000 nid=0x5dc8 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001e390800 nid=0x5b80 runnable [0x000000001e8fe000] 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:170) 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 <0x000000076c18f9d0> (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 <0x000000076c18f9d0> (a java.io.InputStreamReader) at java.io.BufferedReader.readLine(BufferedReader.java:389) at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61) "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001c883000 nid=0x7cdc waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001c882000 nid=0x72e4 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000003158800 nid=0x5f90 in Object.wait() [0x000000001e1ff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076bd870b8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x000000076bd870b8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000003152000 nid=0x39a4 in Object.wait() [0x000000001e0fe000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076bd86af8> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157) - locked <0x000000076bd86af8> (a java.lang.ref.Reference$Lock) "VM Thread" os_prio=2 tid=0x000000001c837800 nid=0x2b18 runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000003078800 nid=0x53b4 runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000000000307a000 nid=0x6f60 runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000307b800 nid=0x6a44 runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000307d000 nid=0x769c runnable "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000307f800 nid=0x6f1c runnable "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000003080800 nid=0x54e4 runnable "VM Periodic Task Thread" os_prio=2 tid=0x000000001e53c800 nid=0x58cc waiting on condition JNI global references: 335 Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x0000000003155c28 (object 0x000000076c07eb80, a java.lang.StringBuilder), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x000000001ef6bed8 (object 0x000000076c07ebc8, a java.lang.StringBuilder), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at com.fhj.jvm.ThreadDeadLock.b(ThreadDeadLock.java:41) - waiting to lock <0x000000076c07eb80> (a java.lang.StringBuilder) - locked <0x000000076c07ebc8> (a java.lang.StringBuilder) at com.fhj.jvm.ThreadDeadLock.lambda$main$1(ThreadDeadLock.java:11) at com.fhj.jvm.ThreadDeadLock$$Lambda$2/455896770.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Thread-0": at com.fhj.jvm.ThreadDeadLock.a(ThreadDeadLock.java:24) - waiting to lock <0x000000076c07ebc8> (a java.lang.StringBuilder) - locked <0x000000076c07eb80> (a java.lang.StringBuilder) at com.fhj.jvm.ThreadDeadLock.lambda$main$0(ThreadDeadLock.java:10) at com.fhj.jvm.ThreadDeadLock$$Lambda$1/2094548358.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) Found 1 deadlock.
可见 Thread-0 和 Thread-1 的线程状态为 BLOCKED,Thread-1 被 Thread-0 挂着,Thread-0 被 Thread-1 挂着。
jcmd 多功能命令行
在JDK 1.7以后,新增了一个命令行工具jcmd。它是一个多功能的工具,可以用来实现前面除了jstat之外所有命令的功能。比如:用它来导出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。jcmd拥有jmap的大部分功能,并且在oracle的官方网站上也推荐使用jcmd命令代jmap命令。
查看帮助:
jcmd -help
基本用法
Usage: jcmd <pid | main class> <command ...|PerfCounter.print|-f file> or: jcmd -l or: jcmd -h command 必须是指定 JVM 可用的有效 jcmd 命令。 可以使用 "help" 命令查看该 JVM 支持哪些命令。 如果指定 pid 部分的值为 0,则会将 commands 发送给所有可见的 Java 进程。 指定 main class 则用来匹配启动类。可以部分匹配。(适用同一个类启动多实例)。 If no options are given, lists Java processes (same as -p). PerfCounter.print 命令可以展示该进程暴露的各种计数器 -f 从文件读取可执行命令 -l 列出(list)本机上可见的 JVM 进程 -h this help
查看进程信息:
jcmd jcmd -l jps -lm 75768 org.jetbrains.idea.maven.server.RemoteMavenServer
这几个命令的结果差不多。可以看到其中有一个 PID 为 75768 的进程,下面看看可以用这个 PID 做什么。
给这个进程发一个 help 指令:
jcmd 75768 help jcmd RemoteMavenServer help
pid 和 main-class 输出信息是一样的:
75768:
The following commands are available:
Compiler.CodeHeap_Analytics
Compiler.codecache
Compiler.codelist
Compiler.directives_add
Compiler.directives_clear
Compiler.directives_print
Compiler.directives_remove
Compiler.queue
GC.class_histogram
GC.class_stats
GC.finalizer_info
GC.heap_dump
GC.heap_info
GC.run
GC.run_finalization
JFR.check
JFR.configure
JFR.dump
JFR.start
JFR.stop
JVMTI.agent_load
JVMTI.data_dump
ManagementAgent.start
ManagementAgent.start_local
ManagementAgent.status
ManagementAgent.stop
Thread.print
VM.class_hierarchy
VM.classloader_stats
VM.classloaders
VM.command_line
VM.dynlibs
VM.flags
VM.info
VM.log
VM.metaspace
VM.native_memory
VM.print_touched_methods
VM.set_flag
VM.start_java_debugging
VM.stringtable
VM.symboltable
VM.system_properties
VM.systemdictionary
VM.uptime
VM.version
help
可以试试这些命令。查看 VM 相关的信息:
# JVM 实例运行时间 jcmd 75768 VM.uptime 596.145 s #JVM 版本号 jcmd 75768 VM.version OpenJDK 64-Bit Server VM version 11.0.6+8-b520.43 JDK 11.0.6 # JVM 实际生效的配置参数 jcmd 75768 VM.flags -XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=8 -XX:G1HeapRegionSize=1048576 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=268435456 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=805306368 -XX:MaxNewSize=482344960 -XX:MinHeapDeltaBytes=1048576 -XX:NonNMethodCodeHeapSize=5836300 -XX:NonProfiledCodeHeapSize=122910970 -XX:ProfiledCodeHeapSize=122910970 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC -XX:-UseLargePagesIndividualAllocation
GC 相关的命令,统计每个类的实例占用字节数。
jcmd 75768 GC.class_histogram
num #instances #bytes class name (module) ------------------------------------------------------- 1: 26469 2722896 [B (java.base@11.0.6) 2: 25224 605376 java.lang.String (java.base@11.0.6) 3: 4278 505368 java.lang.Class (java.base@11.0.6) 4: 2236 337328 [I (java.base@11.0.6) 5: 10031 320992 java.util.concurrent.ConcurrentHashMap$Node (java.base@11.0.6) 6: 3146 276848 java.lang.reflect.Method (java.base@11.0.6) 7: 4617 276176 [Ljava.lang.Object; (java.base@11.0.6) 8: 740 258464 [C (java.base@11.0.6) 9: 6910 221120 java.util.HashMap$Node (java.base@11.0.6) 10: 4765 190600 java.util.LinkedHashMap$Entry (java.base@11.0.6) 11: 1676 168864 [Ljava.util.HashMap$Node; (java.base@11.0.6) 12: 4680 119952 [Ljava.lang.Class; (java.base@11.0.6)
jcmd 75768 help GC.heap_dump
GC.heap_dump Generate a HPROF format dump of the Java heap. Impact: High: Depends on Java heap size and content. Request a full GC unless the '-all' option is specified. Permission: java.lang.management.ManagementPermission(monitor) Syntax : GC.heap_dump [options] <filename> Arguments: filename : Name of the dump file (STRING, no default value) Options: (options must be specified using the <key> or <key>=<value> syntax) -all : [optional] Dump all objects, including unreachable objects (BOOLEAN, false)
# 两者效果差不多; jcmd 需要指定绝对路径; jmap 不能指定绝对路径
jcmd 75768GC.heap_dump -all=true ~/75768-by-jcmd.hprof
jmap -dump:file=./75768-by-jmap.hprof 75768
jcmd 必须指定绝对路径,否则导出的 hprof 文件就以 JVM 所在的目录计算。(因为是发命令交给 JVM 执行的)
jstatd 远程主机信息收集
之前的指令只涉及到监控本机的Java应用程序。为了启用远程监控,需要配合使用jstatd工具。
命令jstatd 是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。
jinfo 工具
诊断工具:jinfo 用来查看具体生效的配置信息以及系统属性,还支持动态增加一部分参数。
看看帮助信息:
jinfo -help
Usage: jinfo [option] <pid> (to connect to running process) jinfo [option] <executable <core> (to connect to a core file) jinfo [option] [server_id@]<remote-IP-hostname> (to connect to remote debug server) where <option> is one of: -flag <name> to print the value of the named VM flag -flag [+|-]<name> to enable or disable the named VM flag -flag <name>=<value> to set the named VM flag to the given value -flags to print VM flags -sysprops to print Java system properties <no option> to print both of the above -h | -help to print this help message
使用示例:
jinfo
74852
jinfo -flags 74852
不加参数过滤,则打印所有信息。
Attaching to process ID 74852, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.121-b13 Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=8388608 -XX:MaxHeapSize=8388608
-XX:MaxNewSize=2621440 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=2621440 -XX:OldSize=5767168
-XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC Command line: -Xmx8m -Xms8m -XX:+PrintGCDetails -javaagent:D:\IDEA\IntelliJ IDEA 2019.3.4\lib\idea_rt.jar=2448:D:\IDEA\IntelliJ IDEA 2019.3.4\bin -Dfile.encoding=UTF-8
可以看到所有的系统属性和启动使用的 VM 参数、命令行参数。非常有利于我们排查问题,特别是去排查一个已经运行的 JVM 里问题,通过 jinfo 我们就知道它依赖了哪些库,用了哪些参数启动。
jrunscript 和 jjs 工具
jrunscript 和 jjs 工具用来执行脚本,只要安装了 JDK 8+,就可以像 shell 命令一样执行相关的操作了。这两个工具背后,都是 JDK 8 自带的 JavaScript 引擎 Nashorn。
执行交互式操作:
$ jrunscript nashorn> 66+88 154
或者:
$ jjs jjs> 66+88 154
按 CTRL+C 或者输入 exit() 回车,退出交互式命令行。
其中 jrunscript 可以直接用来执行 JS 代码块或 JS 文件。比如类似 curl 这样的操作:
jrunscript -e "cat('http://www.baidu.com')"
或者这样:
jrunscript -e "print('hello,kk.jvm'+1)"
甚至可以执行 JS 脚本:
jrunscript -l js -f /XXX/XXX/test.js
而 jjs 则只能交互模式,但是可以指定 JavaScript 支持的 ECMAScript 语言版本,比如 ES5 或者 ES6。
这个工具在某些情况下还是有用的,还可以在脚本中执行 Java 代码,或者调用用户自己的 jar 文件或者 Java 类。
如果是 JDK 9 及以上的版本,则有一个更完善的 REPL 工具——JShell,可以直接解释执行 Java 代码。
参考文档:https://docs.oracle.com/javase/8/docs/technotes/tools/index.html