前面我们了解了虚拟机的相关的技术,大概对虚拟机有一个比较系统的认识。前面的大部分是理论知识,也有些会参杂些实操,但是实际遇到问题还是远远不够的,下面我们通过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
可以用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
如图显示的信息是一个链接,分析内存泄露问题主要会用到“Show heap histogram” 和“OQL”,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语言,使用类似于SQL的语法对内存对象进行查询统计(点击下图中的OQL Help 可以查看其语法)
号外:一般不会用直接使用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
上图中可以看到线程处于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
如上图,根据输出的信息就能确定死锁的位置。以及出现死锁的两个线程互相持有对象以及等待的对象。
号外: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. 虚拟机系列:搞懂虚拟机的日志和日志参数;