![cover](https://img-blog.csdnimg.cn/direct/b49700b5e2754825a308205a8b942343.jpeg)
JVM虚拟机(七)JVM调优的工具
JVM虚拟机(七)JVM调优的工具
目录
![](https://i-blog.csdnimg.cn/blog_migrate/da7cc08aeb7fdeec32c0729787d830c2.jpeg)
一、简介
我们在进行 JVM 调优的时候,通常需要借助一些工具来对系统目前的情况进行分析,从而确定当前的 JVM 是否需要进行调优,以及对哪方面进行调优。
关于 JVM 调优的工具主要分为两大类:
- 命令工具
jps
:查看进程的状态信息。jinfo
:查看进程基本信息,包括启动参数、垃圾回收期类型等。jstack
:查看 Java 进程内线程的堆栈信息。jmap
:查看堆栈信息。jhat
:堆转储快照分析工具。jstat
:JVM 统计监测工具。
- 可视化工具
jconsole
:用于对 JVM 的内存,线程,类的监控。VisualVM
:能够监控线程、内存情况。
下面我们就对上面这些工具进行详细介绍。
二、命令工具
2.1 jps
jps
命令,主要用于查看当前正在运行的 Java 进程的信息。命令用法如下:
# -l选项非必填,主要用于查看更详细的进程信息
jps -l
为了方便理解,举个例子,代码如下所示:
public static void main(String[] args) {
new Thread(() -> {
while (true) {
}
}, "t1").start();
new Thread(() -> {
while (true) {
}
}, "t2").start();
new Thread(() -> {
while (true) {
}
}, "t3").start();
}
代码中 main() 方法中创建了三个线程:t1、t2、t3。这三个线程中都使用了一个 while (true) 死循环,我们执行一下当前 main() 方法,如下所示:
![](image-20240416131132629.png)
打开命令行窗口,输入 jps
命令,可以查看当前正在运行的所有的 Java 程序 ,如下所示:
![](image-20240416131424968.png)
其中 25848 Main 就是我们刚才执行的 main() 方法。以上就是 jps
命令,主要用于查看 Java 进程的信息。
补充: 如果使用 jps 命令看不到完整的 Jar 包名称,可以使用
jps -l
命令。
2.2 jinfo
jinfo
命令,主要用于实时查看和修改正在运行的 Java 应用程序的 JVM 参数和系统属性。
语法如下:
# 查看完整参数
jinfo <pid>
# 查看所有 JVM 标识及其当前值(-禁用+启用)
jinfo -flags <pid>
我们可以使用 java -flags <pid>
来查看当前 Java 进程使用的垃圾回收器类型,如下图所示:
2.3 jstack
jstack
命令,主要用于查看 Java 进程内线程的堆栈信息。用法如下所示:
# pid 就是上面我们用 jps 命令查看到的 Java 进程ID
jstack [options] <pid>
举个例子,上面我们已经用 jps 看到了 main() 方法所在 Java 进程的 PID 是 25848,所以我们直接使用如下命令:
jstack 25848
执行结果如下:
PS D:\IdeaProjects\SpringBootExamples\springboot-demo> jstack 25848
2024-04-16 13:19:58
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode):
"DestroyJavaVM" #23 prio=5 os_prio=0 tid=0x00000000034a4800 nid=0x64e4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"t3" #22 prio=5 os_prio=0 tid=0x0000000022341000 nid=0xebc runnable [0x000000002301f000]
java.lang.Thread.State: RUNNABLE
at com.demo.test.Main.lambda$main$6(Main.java:17)
at com.demo.test.Main$$Lambda$3/2121055098.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
"t2" #21 prio=5 os_prio=0 tid=0x0000000022340000 nid=0x8be0 runnable [0x0000000022f1f000]
java.lang.Thread.State: RUNNABLE
at com.demo.test.Main.lambda$main$5(Main.java:12)
at com.demo.test.Main$$Lambda$2/933699219.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
"t1" #20 prio=5 os_prio=0 tid=0x000000002233f800 nid=0x1518 runnable [0x0000000022e1f000]
java.lang.Thread.State: RUNNABLE
at com.demo.test.Main.lambda$main$4(Main.java:7)
at com.demo.test.Main$$Lambda$1/932172204.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
"Service Thread" #19 daemon prio=9 os_prio=0 tid=0x000000002207f000 nid=0x7338 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread11" #18 daemon prio=9 os_prio=2 tid=0x000000001fa4c800 nid=0x76e8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread10" #17 daemon prio=9 os_prio=2 tid=0x000000001fa51800 nid=0x8f4c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread9" #16 daemon prio=9 os_prio=2 tid=0x000000001fa4d800 nid=0x2fa8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread8" #15 daemon prio=9 os_prio=2 tid=0x000000001fa53800 nid=0x6f58 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread7" #14 daemon prio=9 os_prio=2 tid=0x000000001fa53000 nid=0x8718 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread6" #13 daemon prio=9 os_prio=2 tid=0x000000001fa4a000 nid=0xfd0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread5" #12 daemon prio=9 os_prio=2 tid=0x000000001fa35000 nid=0x7930 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread4" #11 daemon prio=9 os_prio=2 tid=0x000000001fa29000 nid=0x1c04 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001fa27800 nid=0x70a8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001fa26800 nid=0x7dcc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001fa24000 nid=0x8584 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001fa12000 nid=0x32f0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001fa30800 nid=0x3e14 runnable [0x000000002131e000]
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 <0x000000076e4f8ed8> (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 <0x000000076e4f8ed8> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:56)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001f7cb800 nid=0x6ff8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001f81c800 nid=0x93b4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001dd33000 nid=0x6848 in Object.wait() [0x000000002101f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076e1070b8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x000000076e1070b8> (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=0x000000001dd2a800 nid=0x6f90 in Object.wait() [0x000000000291f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076e106af8> (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 <0x000000076e106af8> (a java.lang.ref.Reference$Lock)
"VM Thread" os_prio=2 tid=0x000000001f784800 nid=0x652c runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000034b9800 nid=0x2c08 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000034bb800 nid=0x8e30 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000034bd000 nid=0x7e0c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000034be800 nid=0x6774 runnable
"GC task thread#10 (ParallelGC)" os_prio=0 tid=0x00000000034ca800 nid=0x7fe8 runnable
"GC task thread#11 (ParallelGC)" os_prio=0 tid=0x00000000034ce000 nid=0x68c runnable
"GC task thread#12 (ParallelGC)" os_prio=0 tid=0x00000000034cf000 nid=0x949c runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001fa12800 nid=0x555c waiting on condition
JNI global references: 320
这个信息比较多,它会把当前 Java 进程所对应的所有线程信息打印出来,其中 t1、t2、t3 就是我们刚才自己创建的线程,如下所示:
信息中展示了当前线程对应的是哪一行代码在运行。
假如说程序产生了死锁之后,我们就可以使用当前的 jstack 命令查看当前线程运行的情况,当然也可以查看其它状态线程的运行情况。
以上就是 jstack 命令的使用情况,主要用于查看 Java 进程对应的线程运行情况。
2.4 jmap
jmap
命令,在实际开发用得比较多,主要用于生成堆转内存快照,方便查看内存的使用情况。用法如下:
# 显示 Java 堆的信息
jmap -heap <pid>
# 将 Java 堆信息导出为文件
jmap -dump:format=b,file=heap.hprof <pid>
format=b
:表示以 hprof 二进制格式转储 Java 堆的内存。file=<filename>
用于指定快照 dump 文件的文件名。
用法1:查看堆内存使用情况
举个例子,我们继续使用上面的进程ID 25848,执行如下命令:
jmap -heap 25848
执行结果如下所示:
打印的堆信息是比较多的,并且不太好阅读,为了方便理解,这里手动添加了注释。
PS D:\IdeaProjects\SpringBootExamples\springboot-demo> jmap -heap 25848
Attaching to process ID 25848, please wait...
Debugger attached successfully.
Server compiler detected.
Heap Configuration: // 堆配置
MinHeapFreeRatio = 0 // 空闲堆空间的最小百分比
MaxHeapFreeRatio = 100 // 空闲堆空间的最大百分比
MaxHeapSize = 4125097984 (3934.0MB) // 堆空间允许的最大值
NewSize = 85983232 (82.0MB) // 新生代堆空间的默认值
MaxNewSize = 1374683136 (1311.0MB) // 新生代堆空间允许的最大值
OldSize = 171966464 (164.0MB) // 老年代堆空间的默认值
NewRatio = 2 // 新生代与老年代的堆空间比值,新生代:老年代=1:2
SurvivorRatio = 8 // 两个Survivor区和Eden区的堆空间比值为8,表示S0:S1:Eden=1:1:8
MetaspaceSize = 21807104 (20.796875MB) // 元空间的默认值
CompressedClassSpaceSize = 1073741824 (1024.0MB) // 压缩卷使用空间大小
MaxMetaspaceSize = 17592186044415 MB // 元空间允许的最大值
G1HeapRegionSize = 0 (0.0MB) // 在使用G1垃圾回收器时,JVM会将Heap空间分隔为若干个Region,该参数用来指定每个Region空间的大小。
Heap Usage: // 堆使用情况
PS Young Generation
Eden Space: // Eden区使用情况
capacity = 65011712 (62.0MB)
used = 15607624 (14.884590148925781MB)
free = 49404088 (47.11540985107422MB)
24.007403466009325% used
From Space: // Survivor-From区使用情况
capacity = 10485760 (10.0MB)
used = 0 (0.0MB)
free = 10485760 (10.0MB)
0.0% used
To Space: // Survivor-To区使用情况
capacity = 10485760 (10.0MB)
used = 0 (0.0MB)
free = 10485760 (10.0MB)
0.0% used
PS Old Generation // 老年代使用情况
capacity = 171966464 (164.0MB)
used = 0 (0.0MB)
free = 171966464 (164.0MB)
0.0% used
3512 interned Strings occupying 286240 bytes.
上面我们介绍完了使用 jmap 命令直接查看当前堆内存情况的用法,下面我们再来介绍一下 jmap 的导出用法。
用法2:导出 dump 文件
导出命令语法如下:
# 将 Java 堆信息导出为文件
jmap -dump:format=b,file=heap.hprof <pid>
format=b
:表示以 hprof 二进制格式转储 Java 堆的内存。file=<filename>
用于指定快照 dump 文件的文件名。
概括来说,就是使用 jmap 命令将堆内存生成一个快照,这个快照是一个 dump 二进制文件。
详细来说,生成的 dump 文件是一个进程或系统在某一给定时间的快照。比如在进程崩溃时,甚至是任何时候,我们都可以通过工具将系统或某进程的内存备份出来,供调试分析用。dump 文件中包含了:程序运行的模块信息、线程信息、堆栈调用信息、异常信息等数据,方便系统技术人员进行错误排查。
举个例子,我们继续使用上面的示例来进行操作,执行如下命令:
jmap -dump:format=b,file=d:/hprof/myDump.hprof 25848
执行结果如下所示:
我们来到 d:/hprof/ 目录下,可以看到 dump 文件已经正常生成了,如下所示:
这个 dump 文件可以使用 Eclipse 的 MAT 工具进行分析查看,内容较多,这里就不再介绍了。
以上就是 jmap 命令的使用介绍,它主要用于查看堆内存的使用情况,导出堆内存的 dump 文件。
2.5 jstat
jstat
命令,是 JVM 统计检测工具。可以用来显示垃圾回收信息、类加载信息、新生代统计信息等。
用法1:统计gc信息百分比
语法如下:
jstat -gcutil <pid>
执行结果:
- S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比。
- S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比。
- E:年轻代中Eden(伊甸园)已使用的占当前容量百分比。
- O:old代已使用的占当前容量百分比。
- M:元数据区已使用的占当前容量百分比。
- CCS:压缩类空间已使用的占当前容量百分比。
- YGC :从应用程序启动到采样时年轻代中gc次数。
- YGCT :从应用程序启动到采样时年轻代中gc所用时间(s)。
- FGC :从应用程序启动到采样时old代(全gc)gc次数。
- FGCT :从应用程序启动到采样时old代(全gc)gc所用时间(s)。
- GCT:从应用程序启动到采样时gc用的总时间(s)。
用法2:统计gc的次数及时间
语法如下:
jstat -gc <pid>
执行结果:
- S0C:年轻代中第一个survivor(幸存区)的容量 (字节)。
- S1C:年轻代中第二个survivor(幸存区)的容量 (字节)。
- S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)。
- S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)。
- EC:年轻代中Eden(伊甸园)的容量 (字节)。
- EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)。
- OC:老年代的容量 (字节)。
- OU:老年代目前已使用空间 (字节)。
- MC:metaspace(元空间)的容量 (字节)。
- MU:metaspace(元空间)目前已使用空间 (字节)。
- CCSC:当前压缩类空间的容量 (字节)。
- CCSU:当前压缩类空间目前已使用空间 (字节)。
- YGC:从应用程序启动到采样时年轻代中gc次数。
- YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)。
- FGC:从应用程序启动到采样时老年代(全gc)gc次数。
- FGCT:从应用程序启动到采样时老年代(全gc)gc所用时间(s)。
- GCT:从应用程序启动到采样时gc用的总时间(s)。
以上就是常用的命令工具讲解了,下面我们介绍几个常用的可视化工具。
三、可视化工具
3.1 jconsole
jconsole
工具,主要用于对 JVM 的内存、线程、类进行监控,是一个基于 JMX 的 GUI 性能监控工具。
打开方式:java 安装目录 bin 目录下,如下所示:
直接双击启动 jconsole.exe 就可以,找到我们想要观察的 Java 进行,点击 “连接”。
![](https://i-blog.csdnimg.cn/blog_migrate/8441fdc9ac47e90542f7c33c6c4298bc.png)
点击选择 “不安全的连接”:
![](https://i-blog.csdnimg.cn/blog_migrate/bcfe86ddaa03da9a8508dec41f760130.png)
现在我们就能看到详细的 Java 进程信息了:
当然,除了概览,我们还可以查看内存、线程、类等资源的使用情况。
3.2 VisualVM
VisualVM
工具,能够监控线程、内存情况,查看方法的 CPU 时间和内存中的对象、已被GC的对象,也可以反向查看分配的堆栈。
打开方式:java 安装目录 bin 目录下,直接双击启动 jvisualvm.exe 就行,这个工具目前只有 JDK8 版本自带,其它版本需要使用可以到 Java 官方进行下载。
双击打开之后,界面如下所示:
双击想要查看的 Java 进程即可进行查看:
VisualVM 提供了非常详细的数据展示,供我们对进程中线程、堆、栈的问题进行排查。
以上就是常用 JVM 调优用的全部工具介绍。
整理完毕,完结撒花~ 🌻
更多推荐
所有评论(0)