虚拟机系列:虚拟机性能监控基础工具_JVM

前面我们了解了虚拟机的相关的技术,大概对虚拟机有一个比较系统的认识。前面的大部分是理论知识,也有些会参杂些实操,但是实际遇到问题还是远远不够的,下面我们通过JDK官方的小工具来实践一下。准确的使用工具提升我们定位 ,解决问题的效率。

1. jps: 虚拟机进程状况工具

jps(JVM Process Status Tool) 是虚拟机进程状态的工具,可以列出虚拟机正在运行的虚拟机进程,并像是虚拟机执行主类(Main Class, main()函数所在的类) 名称以及这些进程的本地虚拟机唯一的ID(LVMID,Local Virtual Machine Identifier)。此命令绝对是使用频率最高的JDK命令行工具了(因为其他的JDK工具大多需要输入它查询到的LVMID来确定要监控的是哪一个虚拟机进程)。

对于本地虚拟机进程来说,LVMID与操作系统的进程ID(PID,Process Identifier)是一致的,使用Windows的任务管理器或者UNIX的ps命令也可以查询到虚拟机进程的LVMID,但如果同时启动了多个虚拟机进程,无法根据进程名称定位时,那就必须依赖jps命令显示主类的功能才能区分了。

jps命令格式


jps [ options ] [ hostid ]


如下直接运行jps不加任何参数

ajisun@ajisun-2 /> jps
81746 Launcher
80005
16153 Jps
555 Check

这个输出可以看出当前系统共存在4个应用程序,其中第三个Jps的是这个命令本身(这个命令本质也是一个Java程序)。

Jps 还可以加参数来控制输出(-q, -m, -l,- v)。

jps -q

只输出进程ID(LVMID),省略主类的名称

ajisun@ajisun-2 /> jps -q
51573
537

jps -m

输出虚拟机进程启动时传递给主类main()函数的参数

ajisun@ajisun-2 /> jps -m
51585 Jps -m
537 Check Point/Mobile Access/CShell.jar

jps -l

输出主类的全名,如果进程执行的是jar包,输出jar路径

ajisun@ajisun-2 /> jps -l
51639 sun.tools.jps.Jps
537 /Users/ajisun/Library/Check

jps -v

输出虚拟机进程启动时JVM的参数

ajisun@ajisun-2 /> jps -v
51749 sun.tools.jps.Jps -l -m -v -Denv.class.path=.:/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/lib/tools.jar -Dapplication.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home -Xms8m
537 /Users/ajisun/Library/Check Point/Mobile Access/CShell.jar

2.jinfo: Java配置信息工具

jinfo(Configuration Info for Java) 的作用是实时查看和调整虚拟机各项参数。

基本语法


jinfo [option] {pid}


其中option的可以为

  • -flag {name}打印指定Java虚拟机的参数值。
  • -flag [+|-]{name} 设置指定Java虚拟机参数的布尔值。(+代表true,-代表false)
  • -flag {name}={value} 设置指定Java虚拟机的参数值

例如打印新生代晋升老年代的年龄

ajisun@ajisun-2 /> jinfo -flag MaxTenuringThreshold 11825
-XX:MaxTenuringThreshold=15

例如显示是否打印GC信息

ajisun@ajisun-2 /> jinfo -flag PrintGC 12046
-XX:-PrintGC

除了上面的查找参数外,还支持设置参数(不是所有的参数都支持这种设置)

手动打开gc日志的参数可以动态开启或者关闭

ajisun@ajisun-2 /> jinfo -flag PrintGC 12190  // 查看参数 未设置
-XX:-PrintGC
ajisun@ajisun-2 /> jinfo -flag +PrintGC 12190 //设置参数
ajisun@ajisun-2 /> jinfo -flag PrintGC 12190 // 查看参数已设置
-XX:+PrintGC

3. jmap: Java内存映射工具

jmap(Memory Map for Java) 命令用于生成堆转储快照(一般称为heapdump或dump文件)。jmap的作用并不仅仅是为了获取堆转储快照,它还可以查询finalize执行队列、Java堆和方法区的 详细信息,如空间使用率、当前用的是哪种收集器等。

基本语法


jmap [option] {pid}


其中option可以为

  • -dump:生成java堆转储快照,格式为:-dump:[live,]format=b,file={filename},其中live子参数说明是否只dump出存活对象。
  • -finalizerinfo:显示在F-Queue中等待Finalizer线程执行finalize方法的对象,只在linux/solaris平台下有效。
  • -heap:显示堆详细信息,如使用哪种回收期、参数配置、分带状况等,只在linux/solaris平台下有效。
  • -histo:显示堆中对象统计信息,包括类、实例数量和合计容量。
  • -clstats:以ClassLoader为统计口径显示永久代内存状况,只在linux/solaris平台下有效。
  • -F:当虚拟机进程对-dump选项没有响应时,可以使用这个选项强制生成dump快照,只在linux/solaris平台下有效。

dump导出应用程序的快照

ajisun@ajisun-2 /> jmap -dump:format=b,file=/Users/ajisun/Desktop/jvmTest/s.txt 13157
Dumping heap to /Users/ajisun/Desktop/jvmTest/s.txt ...
Heap dump file created

虚拟机系列:虚拟机性能监控基础工具_JVM_02

可以用jhat或者其他工具分析此快照。

4. jhat: 分析Java应用程序的堆快照内容

jhat(JVM Heap Analysis Tool) 命令是jdk提供的与jmap搭配使用,来分析jmap生成的堆转储快照。

如上文dump的文件

ajisun@ajisun-2 /> jhat /Users/ajisun/Desktop/jvmTest/s.txt
Reading from /Users/ajisun/Desktop/jvmTest/s.txt...
Dump file created Tue Jan 04 15:05:15 CST 2022
Snapshot read, resolving...
Resolving 19265 objects...
Chasing references, expect 3 dots...
Eliminating duplicate references...
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

上面输出是分析后的内容,还可以在浏览器中查看,访问​​http://localhost:7000​

虚拟机系列:虚拟机性能监控基础工具_JVM_03

如图显示的信息是一个链接,分析内存泄露问题主要会用到“Show heap histogram” 和“OQL”,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语言,使用类似于SQL的语法对内存对象进行查询统计(点击下图中的OQL Help 可以查看其语法)

虚拟机系列:虚拟机性能监控基础工具_死锁_04


号外:一般不会用直接使用jhat来分析快照,一是分析快照需要占用较多服务器资源,二是这个分析的比较简单 还有更好的分析工具可用,如VisualVM。


5. jstack: Java堆栈跟踪工具

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

基本语法


jstack [ option ] {pid}


其中option可以为

  • -F: 当正常输出的请求不被响应时,强制输出线程堆栈。
  • -l: 除堆栈外,显示关于锁的信息。

例如

**ajisun@ajisun-2 /> jstack -l 12190**

2022-01-04 19:48:41

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.161-b12 mixed mode):

"Attach Listener" #11 daemon prio=9 os_prio=31 tid=0x00007f8f358db800 nid=0x3407 waiting on condition [0x0000000000000000]

java.lang.Thread.State: RUNNABLE

"Service Thread" #10 daemon prio=9 os_prio=31 tid=0x00007f8f360a8800 nid=0x4003 runnable [0x0000000000000000]

java.lang.Thread.State: RUNNABLE

.......

演示下while死循环信息查看

public class Test {
public static void main(String[] args) {
while (true){
}
}
}
ajisun@ajisun-2 /> jps -l

24513 com.ajisun.coding.ajisunmybatis.Test

ajisun@ajisun-2 /> jstack -l 24513

虚拟机系列:虚拟机性能监控基础工具_jar_05

上图中可以看到线程处于RUNNABLE状态, 在​​com.ajisun.coding.ajisunmybatis.Test.main​​方法的15行出现死循环 。出现死循环,等待,死锁等线程都可以看出具体出现的位置。

死锁的案例

-- 代码如下
public class Test {
private static Object objectA = new Object();
private static Object objectB = new Object();

public static void main(String[] args) {
new Thread(() -> {
synchronized (objectA) {
System.out.println("获取锁objectA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectB) {
System.out.println("获取锁objectB");
}
}
}).start();

new Thread(() -> {
synchronized (objectB) {
System.out.println("获取锁objectB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectA) {
System.out.println("获取锁objectA");
}
}
}).start();
}
}

开始使用jstack命令,先通过jps找到这个类的pid

ajisun@ajisun-2 /> jps -l
25537 com.ajisun.coding.ajisunmybatis.Test
ajisun@ajisun-2 /> jstack -l 25537

虚拟机系列:虚拟机性能监控基础工具_java_06

如上图,根据输出的信息就能确定死锁的位置。以及出现死锁的两个线程互相持有对象以及等待的对象。


号外:jstack不仅可以得到线程的堆栈信息,还能看出是否死锁以及死锁的具体信息。


6. jcmd:多功能命令行

jcmd(Java Command) 虚拟机诊断命令工具,一个多功能的命令,可以导出堆,查看java进程,导出线程信息,执行GC等,从jdk1.7开始提供。

查看当前系统中的虚拟机

ajisun@ajisun-2 /> jcmd -l
36372 com.ajisun.coding.ajisunmybatis.Allocation
36425 sun.tools.jcmd.JCmd -l
11423

查看虚拟机36372支持的命令

ajisun@ajisun-2 /> jcmd 36372 help
36372:
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
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help

上面输出就是此虚拟机支持的命令

  • 查看虚拟机启动的时间
    ​ajisun@ajisun-2 /> jcmd 36372 VM.uptime 36372: 292.741 s ​
  • 输出线程栈信息(和jstack命令输出相同)
    ​ajisun@ajisun-2 /> jcmd 36372 Thread.print ...... "main" #1 prio=5 os_prio=31 tid=0x00007f8729808800 nid=0xe03 runnable [0x0000700002b67000]   java.lang.Thread.State: RUNNABLE at com.ajisun.coding.ajisunmybatis.Allocation.main(Allocation.java:39) "VM Thread" os_prio=31 tid=0x00007f872a816800 nid=0x5003 runnable ...... ​
  • 导出堆信息(和jmap命令功能相同)
    ​ajisun@ajisun-2 /> jcmd 36372 GC.heap_dump /Users/ajisun/Desktop/jvmTest/jcmd.txt 36372: Heap dump file created ​

由​​jcmd 36372 help​​ 命令可以看出,jcmd有很多功能,包括覆盖常用的jmap的功能,而且官方也是推荐使用jcmd替换jmap。

7. jstat:虚拟机统计信息工具

用于监视虚拟机各种运行状态信息的命令工具,是比较强大的,可以用来查看堆信息的详细情况。

由于这个命令篇幅较多,在下一篇文章中详细介绍


说明:以上这些命令中部分会出现错误提示

Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach symbolicator to the process

如果使用的mac电脑,没啥办法,但是在Linux上开启ptrace_scope就没问题的,具体的可以网上搜索下。




我是纪先生,用输出倒逼输入而持续学习,持续分享技术系列文章,以及全网值得收藏好文,欢迎关注公众号,做一个持续成长的技术人。

个人网站



1. 虚拟机系列:jvm运行时堆内存如何分代;

2. 虚拟机系列:jvm中的垃圾回收算法;

3. 虚拟机系列:jvm运行时数据区域;

4. 虚拟机系列:JVM中对象的创建,内存布局和访问定位;

5. 虚拟机系列:JVM中的垃圾收集器;

6. 虚拟机系列:JVM中的内存分配;

7. 虚拟机系列:搞懂虚拟机的日志和日志参数;