如何使用命令行工具监控 JVM 的运行状态?

下面我们总结一下常用的工具及使用方法:

前提条件:

  • 确保已安装 JDK (Java Development Kit) 并且 JAVA_HOME 环境变量已正确配置。
  • 假设我们有一个正在运行的 Java 应用程序。 为了演示,我创建一个简单的 Java 程序,模拟一些特殊情况。

示例 Java 程序 (SimpleApp.java):

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class SimpleApp {

    private static final int LIST_SIZE = 100000;
    private static final int SLEEP_TIME = 100; // Milliseconds

    public static void main(String[] args) throws InterruptedException {
        List<Integer> data = new ArrayList<>();
        Random random = new Random();

        while (true) {
            // Simulate memory allocation
            for (int i = 0; i < 100; i++) {
                data.add(random.nextInt(1000));
            }

            // Simulate some CPU intensive work
            int sum = 0;
            for (Integer num : data) {
                sum += num;
            }

            // Simulate some IO wait
            Thread.sleep(SLEEP_TIME);

            // Periodically clear some memory
            if (data.size() > LIST_SIZE) {
                data.subList(0, LIST_SIZE / 2).clear();
                System.out.println("List size: " + data.size());
            }
        }
    }
}
  1. 编译和运行 SimpleApp.java:

    bash 复制代码
    javac SimpleApp.java
    java SimpleApp

    让程序在后台运行。 可以使用 nohup java SimpleApp & 以确保程序在终端关闭后可以继续运行。

监控步骤及输出示例:

1. 使用 jps 找到进程 ID (PID):

bash 复制代码
  jps -l

输出:

bash 复制代码
 12345 SimpleApp
 67890 sun.tools.jps.Jps  // jps 进程本身

我们一定要记住 SimpleApp 的进程 ID (在这个例子中是 12345)。 之后所有的命令都需要用到这个 ID。

2. 使用 jstat 监控 JVM 统计信息:

  • 监控 GC 汇总信息:
bash 复制代码
 jstat -gc 12345 1000  // 每秒刷新一次

输出

bash 复制代码
           S0C      S1C      S0U      S1U    EC       EU       OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
        15360.0  15360.0   0.0     1024.0 256000.0 131072.0  524288.0   262144.0  11264.0 10496.0 1280.0 1152.0   23     0.256   2      0.123    0.379
        15360.0  15360.0   0.0     1024.0 256000.0 131072.0  524288.0   262144.0  11264.0 10496.0 1280.0 1152.0   23     0.256   2      0.123    0.379
        15360.0  15360.0   0.0     1024.0 256000.0 131072.0  524288.0   262144.0  11264.0 10496.0 1280.0 1152.0   23     0.256   2      0.123    0.379

解释

  • S0C, S1C: Survivor 0/1 的容量 (Capacity)。

    • S0U, S1U: Survivor 0/1 的使用量 (Utilization)。
    • EC: Eden 区的容量。
    • EU: Eden 区的使用量。
    • OC: 老年代的容量。
    • OU: 老年代的使用量。
    • MC: Metaspace 的容量。
    • MU: Metaspace 的使用量。
    • CCSC: 压缩类空间 (Compressed Class Space) 的容量。
    • CCSU: 压缩类空间的使用量。
    • YGC: Young GC (Minor GC) 的次数。
    • YGCT: Young GC 的总时间。
    • FGC: Full GC 的次数。
    • FGCT: Full GC 的总时间。
    • GCT: 总 GC 时间。
  • 监控 GC 容量:

bash 复制代码
jstat -gccapacity 12345

输出示例:

bash 复制代码
    NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         MC     CCSC    YGC   FGC
 21504.0 699392.0  28672.0 15360.0 15360.0  28672.0    532480.0  1398784.0   532480.0   11264.0 1280.0     23     2

解释:

  • NGCMN: 新生代最小容量。

  • NGCMX: 新生代最大容量。

  • NGC: 新生代当前容量。

  • S0C, S1C, EC: 和上面相同。

  • OGCMN: 老年代最小容量。

  • OGCMX: 老年代最大容量。

  • OGC: 老年代当前容量。

  • MC: Metaspace 容量。

  • CCSC: 压缩类空间容量。

  • YGC, FGC: Young GC 和 Full GC 次数。

  • 监控 GC 利用率:

    bash 复制代码
    jstat -gcutil 12345

    输出示例:

    复制代码
      S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
     0.00   6.67  52.25  50.00  93.20  89.84     23     0.256     2    0.123    0.379

    解释:

    • S0, S1: Survivor 0/1 使用率。
    • E: Eden 区使用率。
    • O: 老年代使用率。
    • M: Metaspace 使用率。
    • CCS: 压缩类空间使用率。
    • YGC, YGCT, FGC, FGCT, GCT: 和上面相同。

3. 使用 jinfo 查看 JVM 配置:

bash 复制代码
    jinfo -flags 12345

输出示例:

bash 复制代码
 Attaching to process ID 12345, please wait...
 Debugger attached successfully.
 Server compiler detected.
 JVM version is 21.0.2+13-LTS-58
 Non-default VM flags:
 -XX:CICompilerCount=3
 -XX:InitialHeapSize=268435456
 -XX:MaxHeapSize=4294967296
 -XX:MetaspaceSize=21807104
 -XX:+UseCompressedClassPointers
 -XX:+UseCompressedOops
 -XX:+UseSerialGC
 Command line:
 -java.compiler=NONE

解释: 我们可以看到堆大小 (InitialHeapSize, MaxHeapSize),GC 算法 (UseSerialGC) 等信息。

4. 使用 jstack 查看线程堆栈信息:

bash 复制代码
 jstack 12345

输出示例 (节选):

bash 复制代码
 Full thread dump OpenJDK 64-Bit Server VM (21.0.2+13-LTS-58, mixed mode, sharing):

 "main" #1 prio=5 os_prio=0 cpu=5.23ms elapsed=44.25s tid=0x00000176d88845d0 nid=0x307c runnable  [0x0000005f4d2ffd80]
    java.lang.Thread.State: RUNNABLE
         at SimpleApp.main(SimpleApp.java:23)

 "Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=44.25s tid=0x00000176d8897330 nid=0x3114 waiting on condition  [0x0000005f4d3ff000]
    java.lang.Thread.State: WAITING (on object monitor)
         at java.lang.Object.wait([email protected]/Native Method)
         - waiting on <0x00000000c0000540> (a java.lang.ref.Reference$Lock)
         at java.lang.Object.wait([email protected]/Object.java:339)
         at java.lang.ref.Reference.waitForReferencePendingList([email protected]/Reference.java:241)
         - locked <0x00000000c0000540> (a java.lang.ref.Reference$Lock)
         at java.lang.ref.Reference.processPendingReferences([email protected]/Reference.java:213)
         at java.lang.ref.Reference$ReferenceHandler.run([email protected]/Reference.java:174)

 "Finalizer" #3 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=44.25s tid=0x00000176d8897e90 nid=0x30f8 in Object.wait()  [0x0000005f4d4ff000]
    java.lang.Thread.State: WAITING (on object monitor)
         at java.lang.Object.wait([email protected]/Native Method)
         - waiting on <0x00000000c0000630> (a java.lang.ref.Finalizer$Lock)
         at java.lang.ref.Finalizer$FinalizerThread.run([email protected]/Finalizer.java:172)

解释: 显示了每个线程的堆栈跟踪。 main 线程正在执行 SimpleApp.main 方法,并且处于 RUNNABLE 状态。 其他线程 (如 Reference Handler, Finalizer) 是 JVM 内部线程。

5. 使用 jmap 生成 Heap Dump 并分析:

bash 复制代码
 jmap -dump:format=b,file=heapdump.bin 12345

这将生成一个名为 heapdump.bin 的文件。 你可以使用诸如 Eclipse Memory Analyzer Tool (MAT) 或 VisualVM 之类的工具来分析这个文件。

  • 使用 jmap -histo (谨慎使用):

    bash 复制代码
    jmap -histo 12345 | head -n 20  # 只显示前20行

    输出示例:

    复制代码
     num      #instances         #bytes  className
    ----------------------------------------------
       1:           325316      37106776  java.lang.Integer
       2:            99991       2399784  java.util.ArrayList
       3:            32328        775872  java.lang.String
       4:             2779        239824  [B
       5:             1501        126424  java.util.HashMap$Node
       6:              403         49568  java.lang.Class
       7:              829         33160  [C
       8:              446         28544  java.lang.reflect.Method
       9:              457         21936  java.lang.invoke.MethodHandleNatives$Lookup
      10:              373         14920  java.util.concurrent.ConcurrentHashMap$Node
      11:              372         11904  java.lang.invoke.MethodHandle$PolymorphicInvoker
      12:              361         11552  java.util.LinkedHashMap$Entry
      13:              278          8896  [Ljava.lang.Object;
      14:              141          8472  java.util.HashMap
      15:               92          7360  java.lang.invoke.MethodHandle
      16:              150          6000  java.io.FileDescriptor
      17:              185          5920  java.util.LinkedHashMap
      18:              135          5400  sun.nio.cs.UTF_8$Encoder
      19:               65          5200  java.util.WeakHashMap$Entry

    解释: 显示了堆中各个类别的实例数量和总字节数。 可以看到 java.lang.Integer 占用了大量的内存,这符合我们的程序逻辑。
    警告 : jmap -histo 可能导致 JVM 停顿一段时间,尤其是堆很大的时候。 尽量使用Heap Dump文件分析代替它.

6. 使用 jcmd (推荐):

  • 查看所有可用的命令:

    bash 复制代码
    jcmd 12345 help
  • 获取 GC 统计信息:

    bash 复制代码
    jcmd 12345 GC.stats

    输出示例: (类似 jstat -gc 的输出,但更详细)

    bash 复制代码
     12345:
     Timestamp: 2024-01-26T14:30:00.123+0800
     Full GC count: 2
     Full GC elapsed time: 0.123 seconds
    
     Young GC count: 23
     Young GC elapsed time: 0.256 seconds
    
     Heap memory usage:
       Committed: 819200 KB
       Used: 524288 KB
    
     Eden space:
       Capacity: 262144 KB
       Used: 131072 KB
  • 打印线程堆栈信息 (包含锁信息):

    bash 复制代码
    jcmd 12345 Thread.print -l

    输出示例: (类似 jstack -l 的输出)

  • 请求 Full GC (谨慎使用):

    bash 复制代码
    jcmd 12345 GC.run_fullgc

    警告: 手动触发 Full GC 可能会导致应用程序停顿。 只在必要时使用.

  • 创建 Heap Dump:

    bash 复制代码
    jcmd 12345 GC.heap_dump filename=heapdump.hprof

实践:

  • jps 是基础: 始终先用 jps 找到 PID。
  • jstat 监控: 使用 jstat 监控一段时间,观察 GC 频率,内存使用情况。 如果 Young GC 太频繁,说明新生代太小。 如果 Full GC 频繁,说明老年代或者 Metaspace 内存不够。
  • jinfo 查看配置: 使用 jinfo 确认 JVM 参数是否符合预期。
  • jstack 查线程: 当发现 CPU 占用率高,或者程序卡顿的时候, 使用 jstack 查看线程状态,找出问题线程。
  • jmap 分析堆: 当怀疑内存泄漏时, 使用 jmap 生成 Heap Dump, 使用 MAT 或 VisualVM 等工具进行分析。
  • jcmd 是首选: 尽可能使用 jcmd,因为它更强大和灵活。
  • 监控频率: 合理设置监控频率。 频繁的监控会增加 JVM 的负担, 太低的频率可能无法及时发现问题。

案例分析:

假设在使用 jstat 监控一段时间后,发现 FGC (Full GC) 的次数在不断增加,而且 FGCT (Full GC 时间) 也很长。 这说明程序可能存在内存泄漏,或者老年代内存不足。

  1. 使用 jmap -histo:live <PID>: 观察存活对象,找出占用内存最多的对象,初步判断可能的内存泄漏点。
  2. 使用 jmap -dump:format=b,file=heapdump.bin <PID>: 生成 Heap Dump 文件。
  3. 使用 Eclipse MAT 或 VisualVM 打开 heapdump.bin: 分析对象引用链,找出是谁持有了这些对象,导致它们无法被回收。

总结:

通过这些命令行工具,我们可以了解 JVM 的运行状态,及时发现和解决性能问题。 jcmd 是一个非常强大的工具,建议深入学习和使用。

相关推荐
碎梦归途2 小时前
23种设计模式-结构型模式之享元模式(Java版本)
java·开发语言·jvm·设计模式·享元模式
左灯右行的爱情3 小时前
JVM-卡表
java·jvm·算法
程序猿chen3 小时前
JVM考古现场(二十五):逆熵者·时间晶体的永恒之战(进阶篇)
java·jvm·git·后端·程序人生·java-ee·改行学it
web安全工具库4 小时前
Python内存管理之隔代回收机制详解
java·jvm·算法
Elastic 中国社区官方博客6 小时前
Elasticsearch 堆内存使用情况和 JVM 垃圾回收
大数据·jvm·数据库·elasticsearch·搜索引擎·全文检索
芦屋花绘11 小时前
Java的JUC详细全解
java·开发语言·jvm·spring boot·kafka
长安思15 小时前
在C#串口通信中,一发一收的场景,如何处理不同功能码的帧数据比较合理,代码结构好
java·jvm·算法
jieyucx16 小时前
C++中的引用:深入理解与实用示例
java·jvm·c++
碎梦归途1 天前
23种设计模式-创建型模式之原型模式(Java版本)
java·开发语言·jvm·设计模式·原型模式