📚 残梦三生

记录技术成长,分享学习心得

← 返回首页

虚拟机性能监控和处理

分类:Java | 日期:2023-12-07

1 简介

众所周知,Java的一大特点是平台无关性。所谓的平台无关性就是Java是一次编写,到处运行,Java之所以能做到这一点,最主要的就是因为它是运行在虚拟机上的,通过虚拟机来实现跨平台使用。但是有些人对于虚拟机的了解仅到此而已,并不知道在程序运行时,如何检测虚拟机的性能,或者当程序发生问题时,如何处理故障。

2 工具介绍

随着JDK版本的更迭,JDK的bin目录下的小工具的数量和功能也越发的强大,在很多场景都可以使用到这些工具。

下面,我们需要运行一段代码,并且通过这些工具,来找到所运行的代码存在说明问题。

public class Demo {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        final Thread task1 = new Thread(new Task());
        final Thread task2 = new Thread(new Task());
        final Thread task3 = new Thread(new Task());
        task3.start();
        task1.start();
        task2.start();
    }
    private static class Task implements Runnable {
        @Override
        public void run() {
            lock.lock();
            int i = 0;
            while (true) {
                i++;
            }
        }
    }
}

2.1 虚拟机进程状态

首先,我们通过JDK工具中经典的 jps 工具。通过这个命令,可以列出所有正在运行的虚拟机进程,并显示虚拟机执行主类名称和唯一ID。

通过使用 jps -l 命令,就可以将所有的的运行进程列表展示出来。运行的Demo的ID号为 183496。

jps -l
186512 org.jetbrains.jps.cmdline.Launcher
183496 org.example.Demo
187420 jdk.jcmd/sun.tools.jps.Jps

2.2 统计信息

jstat 是用于监视虚拟机各种运行状态信息的命令行工具,通过这个命令,可以显示虚拟机中类加载、内存、垃圾回收等运行时数据。

jstat -gc 183496
S0C         S1C         S0U         S1U          EC           EU           OC           OU          MC         MU       CCSC      CCSU     YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT
   0.0         0.0         0.0         0.0      28672.0       4096.0     487424.0          0.0        0.0        0.0       0.0       0.0      0     0.000     0     0.000     0     0.000     0.000

如上图数据所示,S0C、S1C分别代表第一、第二幸存区的大小;S0U、S1U代表第一、第二幸存区的使用大小;EC、EU代表伊甸园区的大小和使用大小;OC、OU代表老年代大小和使用大小;MC、MU代表发放区大小和使用大小;CCSC、CCSU分别是压缩类空间大小和使用大小;YGC、YGCT则代表年轻代垃圾回收次数和消耗的时间;FGC、FGCT是老年代垃圾回收次数和消耗时间;CGC、CGCT表示并发垃圾回收次数和消耗时间;GCT则表示垃圾回收总消耗时间。

2.3 Java配置信息

jinfo 是实时查看和调整虚拟机各项参数的工具,它可以查看到运行的Java版本、CPU型号,系统,程序名称等信息。

jinfo 183496
Java System Properties:
java.specification.version=17
sun.cpu.isalist=amd64
sun.jnu.encoding=GBK
...
os.name=Windows 11
java.vm.specification.version=17
sun.java.launcher=SUN_STANDARD
user.country=CN
sun.boot.library.path=C\:\\Program Files\\Java\\jdk-17.0.4.1\\bin
sun.java.command=org.example.Demo
...

2.4 Java堆栈跟踪工具

jstack 命令是用于虚拟机当前时刻的线程快照,也就是当前虚拟机内每一条线程正常在执行的方法堆栈的集合。主要是用来定位线程出现长时间停顿的原因,如线程间死锁、死循环等。

jstack 183496
...
"Thread-2" #26 prio=5 os_prio=0 cpu=2875890.62ms elapsed=3618.37s tid=0x000001c2ff83bda0 nid=0x2cca4 runnable  [0x000000464c6fe000]
   java.lang.Thread.State: RUNNABLE
        at org.example.Demo$Task.run(Demo.java:22)
        at java.lang.Thread.run(java.base@17.0.4.1/Thread.java:833)

   Locked ownable synchronizers:
        - <0x0000000628a65e90> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

"Thread-0" #24 prio=5 os_prio=0 cpu=0.00ms elapsed=3618.37s tid=0x000001c2ff83c270 nid=0x2cad8 waiting on condition  [0x000000464c7ff000]
   java.lang.Thread.State: WAITING (parking)
        at jdk.internal.misc.Unsafe.park(java.base@17.0.4.1/Native Method)
        - parking to wait for  <0x0000000628a65e90> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(java.base@17.0.4.1/LockSupport.java:211)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.4.1/AbstractQueuedSynchronizer.java:715)
        at java.util.concurrent.locks.ReentrantLock$Sync.lock(java.base@17.0.4.1/ReentrantLock.java:153)
        at java.util.concurrent.locks.ReentrantLock.lock(java.base@17.0.4.1/ReentrantLock.java:322)
        at org.example.Demo$Task.run(Demo.java:19)
        at java.lang.Thread.run(java.base@17.0.4.1/Thread.java:833)

   Locked ownable synchronizers:
        - None

"Thread-1" #25 prio=5 os_prio=0 cpu=0.00ms elapsed=3618.37s tid=0x000001c2ff841760 nid=0x28e88 waiting on condition  [0x000000464c8ff000]
   java.lang.Thread.State: WAITING (parking)
        at jdk.internal.misc.Unsafe.park(java.base@17.0.4.1/Native Method)
        - parking to wait for  <0x0000000628a65e90> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(java.base@17.0.4.1/LockSupport.java:211)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.4.1/AbstractQueuedSynchronizer.java:715)
        at java.util.concurrent.locks.ReentrantLock$Sync.lock(java.base@17.0.4.1/ReentrantLock.java:153)
        at java.util.concurrent.locks.ReentrantLock.lock(java.base@17.0.4.1/ReentrantLock.java:322)
        at org.example.Demo$Task.run(Demo.java:19)
        at java.lang.Thread.run(java.base@17.0.4.1/Thread.java:833)

   Locked ownable synchronizers:
        - None

如上图所示,Thread-2的线程处于运行状态(RUNNABLE),持有"0x0000000628a65e90"的锁;Thread-0线程处于等待状态(WAITING),等待"0x0000000628a65e90"的锁;Thread-1线程处于等待状态(WAITING),也在等待"0x0000000628a65e90"的锁。

由此可见,该demo的问题是因为Thread-2长时间占用着锁,不释放,最终导致了出现死锁的情况出现。

3 总结

JDK自带的工具是我们开发过程中非常重要的助手,掌握它们的使用对于提高开发效率和代码质量至关重要。本文中列举的只是比较常用的命令。具体还有其他好用的工具,还需要平时积累,通过阅读文档、实际项目应用等方式来学习和掌握它们的使用。