技术演进中的开发沉思-330 : 虚拟机命令行工具

排查 JVM 问题的 "第一反应" 永远是命令行工具 ------ 线上系统出问题时,GUI 工具(如 JVisualVM)往往连不上,而 jps、jstat、jmap、jstack 这些命令行工具,是排查问题的 "硬通货"。早年做电商秒杀系统时,线上突然出现接口超时,我先用 jps 找到进程 ID,再用 jstat 看 GC 状态,发现 YGC 每秒 3 次、YGCT 占比 80%,判断是新生代 GC 频繁;接着用 jstack 排查线程,发现大量线程阻塞在数据库连接池,最终定位到连接池参数过小的问题,5 分钟就解决了故障。这些命令行工具看似简单,却是读懂 JVM 运行状态、快速定位问题的核心 ------ 新手会用 IDE 写代码,资深开发者会用命令行 "诊断" JVM。

一、jps

jps(JVM Process Status Tool)是最基础的 JVM 命令行工具,核心作用是列出当前系统中运行的 Java 虚拟机进程,并显示进程 ID(LVMID,本地虚拟机 ID)和主类名 ------ 它就像 "点名器",先找到要诊断的进程,才能用其他工具分析。

1. 核心用法

jps 的用法非常简单,无需复杂参数,常用命令如下:

复制代码
# 基础用法:列出LVMID和主类名
jps

# 进阶用法1:-l 显示主类的全限定名(或JAR包路径)
jps -l

# 进阶用法2:-v 显示JVM启动参数
jps -v

# 进阶用法3:-m 显示传递给主类main方法的参数
jps -m

2. 实战输出与解读

以一个 Spring Boot 应用为例,运行jps -lv的输出如下:

复制代码
1234 org.springframework.boot.loader.JarLauncher -Xms2G -Xmx2G -XX:+UseG1GC
5678 sun.tools.jps.Jps -Dapplication.home=/usr/local/jdk1.8.0_301 -Xms8m
  • 1234:LVMID(和系统 PID 一致,可通过ps -ef | grep java验证);
  • org.springframework.boot.loader.JarLauncher:Spring Boot 应用的主类全限定名;
  • -Xms2G -Xmx2G -XX:+UseG1GC:JVM 启动参数;
  • 5678:jps 自身的进程 ID(可忽略)。

3. 实战踩坑点

  • Windows 下 jps 看不到进程:通常是 JDK 环境变量配置错误(比如 PATH 指向 JRE 而非 JDK),jps 属于 JDK 的 bin 目录,JRE 中没有;
  • Linux 下 jps 无输出:权限问题 ------ 当前用户没有权限访问目标 Java 进程的临时文件(/tmp/hsperfdata_xxx),切换到进程所属用户即可;
  • LVMID 和 PID 的关系:在本地虚拟机中,LVMID 等于系统 PID;在远程虚拟机(如 JDK 的远程调试)中,LVMID 可能不同。

4. 核心价值

不管是用 jstat 监控 GC,还是用 jmap 生成堆快照,第一步都是用 jps 找到 LVMID------ 这是所有 JVM 诊断的 "起点",也是新手最易忽略的基础步骤。

二、jstat

jstat(JVM Statistics Monitoring Tool)是 JVM 的 "实时监控仪",核心作用是监控 JVM 的类加载、内存使用、GC 状态、JIT 编译等数据,支持周期性输出,是排查 GC 问题的 "第一利器"。

1. 核心用法

jstat 的参数很多,但最常用的是-gc(监控 GC 状态),语法如下:

复制代码
# 基本语法:jstat -gc [LVMID] [采样间隔(ms)] [采样次数]
# 示例:监控1234进程,每1000ms采样一次,共采样10次
jstat -gc 1234 1000 10

2. 输出字段解读(新手必记)

运行jstat -gc 1234的典型输出如下:

复制代码
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
 5120.0 5120.0  0.0    2560.0 40960.0  20480.0   81920.0    40960.0   25600.0 24576.0 3072.0 2867.2   120    1.800   5      0.500    2.300

每个字段的含义:

字段 含义 实战价值
S0C/S1C Survivor0/1 区容量(KB) 看 Survivor 区大小是否合理
S0U/S1U Survivor0/1 区已使用(KB) 看 Survivor 区是否有对象存活
EC/EU Eden 区容量 / 已使用(KB) EU 接近 EC 时,即将触发 Minor GC
OC/OU 老年代容量 / 已使用(KB) OU 接近 OC 时,即将触发 Major GC
YGC/YGCT 新生代 GC 次数 / 总耗时(秒) YGC 频繁(如每秒 1 次)→ 新生代过小或对象创建过快
FGC/FGCT Full GC 次数 / 总耗时(秒) FGC 频繁→ 老年代内存泄漏或参数设置不合理
GCT GC 总耗时(秒) GCT 占比 CPU 时间过高→ GC 成为性能瓶颈

3. 用 jstat 定位新生代 GC 频繁问题

我曾排查一个电商秒杀系统的性能问题,步骤如下:

  1. jps找到进程 ID:1234;

  2. jstat -gc 1234 1000 5监控 GC:

    复制代码
    # 第1次采样
    S0C S1C S0U S1U EC EU OC OU YGC YGCT FGC FGCT GCT
    5120 5120 0 0 40960 40950 81920 40960 120 1.8 5 0.5 2.3
    # 第2次采样(1秒后)
    5120 5120 2560 0 40960 100 81920 41960 121 1.815 5 0.5 2.315
  3. 分析:Eden 区 EU 从 40950(接近满)降到 100,YGC 从 120 涨到 121,说明 1 秒内触发了 1 次 Minor GC,YGC 频率过高;

  4. 解决:调大新生代内存(-Xmn从 50M 调到 100M),YGC 频率从每秒 1 次降到每 10 秒 1 次,接口响应时间减少 30%。

4. 其他常用参数

  • -gcutil:以百分比显示 GC 状态(更直观),示例:

    bash

    复制代码
    jstat -gcutil 1234 1000 5
    # 输出:S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
    #        0.0 50.0 50.0 50.0 96.0 93.0 120 1.8 5 0.5 2.3
  • -class:监控类加载状态,看是否有频繁的类加载 / 卸载(如动态代理导致);

  • -compiler:监控 JIT 编译状态,看编译耗时是否过高。

三、jmap

jmap(Memory Map for Java)是 JVM 的 "内存快照机",核心作用是生成堆转储快照(hprof 文件)、查看堆内存布局、统计对象数量------ 它是排查内存泄漏、OOM 问题的核心工具。

1. 生成堆转储快照(-dump)

生成堆快照是 jmap 最常用的功能,语法如下:

复制代码
# 基本语法:jmap -dump:format=b,file=<文件名> <LVMID>
# 示例:生成1234进程的堆快照,保存为heap.hprof
jmap -dump:format=b,file=heap.hprof 1234
  • format=b:指定快照格式为二进制(必须);
  • file=heap.hprof:快照文件名,后缀建议用.hprof(方便 MAT/Jhat 分析)。

2. 查看堆内存布局(-heap)

复制代码
# 查看1234进程的堆内存布局和JVM参数
jmap -heap 1234

输出示例(核心部分):

复制代码
Attaching to process ID 1234, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.301-b09

using thread-local object allocation.
Garbage-First (G1) GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 2147483648 (2048.0MB)
   InitialHeapSize          = 2147483648 (2048.0MB)
   NewSize                  = 1073741824 (1024.0MB)
   MaxNewSize               = 1073741824 (1024.0MB)
   OldSize                  = 1073741824 (1024.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   ...

Heap Usage:
G1 Heap:
   regions  = 512
   capacity = 2147483648 (2048.0MB)
   used     = 1073741824 (1024.0MB)
   free     = 1073741824 (1024.0MB)
   50.0% used
G1 Young Generation:
Eden Space:
   regions  = 200
   capacity = 858993459 (819.2MB)
   used     = 858993459 (819.2MB)
   free     = 0 (0.0MB)
   100.0% used
Survivor Space:
   regions  = 10
   capacity = 41943040 (40.0MB)
   used     = 20971520 (20.0MB)
   free     = 20971520 (20.0MB)
   50.0% used
G1 Old Generation:
   regions  = 100
   capacity = 1228800000 (1171.9MB)
   used     = 209715200 (200.0MB)
   free     = 1019084800 (971.9MB)
   17.0% used

通过jmap -heap能快速查看:

  • GC 收集器类型(如 G1 GC);
  • 堆内存配置(-Xms/-Xmx/-Xmn);
  • 各区域的内存使用情况(如 Eden 区 100% 使用,即将触发 Minor GC)。

3. 踩坑点

  • 生成快照时进程暂停 :jmap 生成 dump 时,JVM 会暂停所有线程(STW),生产环境需在低峰期执行,或使用-XX:+HeapDumpOnOutOfMemoryError(OOM 时自动生成快照,无需手动执行);
  • 大堆快照生成慢:若堆内存为 16G,生成快照可能需要几分钟,且快照文件大小接近堆内存大小,需确保磁盘空间充足;
  • Linux 下权限问题:需用进程所属用户执行 jmap,否则会提示 "Permission denied"。

4. 核心价值

生成堆快照后,可通过 jhat 或 MAT 分析,找到占用内存最多的对象、内存泄漏的根源 ------ 我曾通过堆快照发现,一个静态 Map 持有大量订单对象的强引用,导致内存泄漏,清理后 OOM 问题彻底解决。

四、jstack

jstack(Stack Trace for Java)是 JVM 的 "线程透视镜",核心作用是生成线程快照(线程栈),查看线程状态、排查死锁和线程阻塞------ 线上系统出现接口超时、CPU 100%,大概率要靠 jstack 定位问题。

1. 核心用法

复制代码
# 基本语法:jstack -l <LVMID> > <输出文件>
# 示例:生成1234进程的线程快照,保存为thread.log(方便分析)
jstack -l 1234 > thread.log
  • -l:显示锁的详细信息(必须加,否则看不到死锁和锁等待信息);
  • 输出到文件:线程快照内容较多,直接输出到文件更易分析。

2. 排查死锁问题

jstack 能自动检测死锁,线程快照中会明确标注 "Found one Java-level deadlock",示例如下:

复制代码
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f8b10006800 (object 0x000000076ab82000, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f8b10008c00 (object 0x000000076ab82010, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.example.DeadlockDemo$2.run(DeadlockDemo.java:25)
        - waiting to lock <0x000000076ab82000> (a java.lang.Object)
        - locked <0x000000076ab82010> (a java.lang.Object)
"Thread-0":
        at com.example.DeadlockDemo$1.run(DeadlockDemo.java:15)
        - waiting to lock <0x000000076ab82010> (a java.lang.Object)
        - locked <0x000000076ab82000> (a java.lang.Object)

Found 1 deadlock.

分析步骤:

  1. 找到死锁线程(Thread-0 和 Thread-1);
  2. 看每个线程持有的锁和等待的锁:Thread-0 持有 0x000000076ab82000,等待 0x000000076ab82010;Thread-1 相反;
  3. 解决:调整锁的获取顺序,避免循环等待。

3. 排查线程阻塞问题

我曾排查一个接口超时问题,步骤如下:

  1. jstack -l 1234 > thread.log生成线程快照;

  2. 搜索BLOCKED状态的线程:

    复制代码
    "http-nio-8080-exec-10" #50 daemon prio=5 os_prio=0 tid=0x00007f8b08001000 nid=0x1234 waiting for monitor entry [0x00007f8af8000000]
    java.lang.Thread.State: BLOCKED (on object monitor)
            at com.example.OrderService.queryOrder(OrderService.java:50)
            - waiting to lock <0x000000076ab83000> (a com.example.OrderService)
            at com.example.OrderController.query(OrderController.java:20)
  3. 分析:大量线程阻塞在OrderService.queryOrder方法,等待锁 0x000000076ab83000;

  4. 定位:该方法用了synchronized修饰,且执行时间过长(数据库查询慢),导致线程排队;

  5. 解决:优化数据库查询,将synchronized改为细粒度锁,线程阻塞问题消失。

4. 核心价值

线上系统的 CPU 100%、接口超时、死锁,90% 能通过 jstack 定位 ------ 新手要学会搜索以下关键词:

  • DEADLOCK:死锁;
  • BLOCKED:线程阻塞;
  • RUNNABLE但 CPU 高:线程执行死循环;
  • WAITING (parking):线程等待(如线程池队列满、锁等待)。

五、jhat:

jhat(Java Heap Analysis Tool)是 JVM 的堆快照分析工具,核心作用是解析堆转储快照,提供 HTTP 访问界面,查看对象分布、引用链------ 它是 jmap 的 "配套工具",能将二进制的 hprof 文件转为可视化的网页。

1. 分析堆快照

复制代码
# 基本语法:jhat <堆快照文件>
# 示例:分析heap.hprof文件
jhat heap.hprof

运行后,jhat 会启动一个 HTTP 服务器(默认端口 7000),访问http://localhost:7000即可查看堆快照分析结果。

2. 核心功能(网页界面)

  • 显示所有类的对象数量和大小 :找到占用内存最多的类(如java.util.HashMap);
  • 显示对象的引用链:找到某个大对象被谁引用(如静态 Map);
  • OQL 查询 :用类似 SQL 的语法查询对象(如select * from com.example.Order);
  • 死锁检测:查看堆中的死锁对象。

3. 实战踩坑点

  • 大快照分析慢:若堆快照为 8G,jhat 可能需要几十分钟才能加载完成,且占用大量内存;
  • 功能不如 MAT 强大:jhat 的功能较基础,生产环境更推荐用 Eclipse MAT(Memory Analyzer Tool)分析堆快照;
  • Linux 下需开放端口:若在远程服务器运行 jhat,需开放 7000 端口,否则无法访问。

4. 核心价值

jhat 适合新手快速分析堆快照,熟悉堆内存的分布 ------ 但资深开发者通常用 MAT(功能更全、速度更快),jhat 可作为 "应急工具"。

六、命令行工具的使用顺序

二十余年的实战经验,我总结出 JVM 问题排查的 "工具使用顺序",能快速定位 90% 的问题:

  1. jps:找到目标进程 ID;
  2. jstat:监控 GC 状态,判断是 GC 问题还是线程问题;
  3. jstack:若 GC 正常,排查线程(死锁、阻塞、死循环);
  4. jmap:若 GC 异常,生成堆快照;
  5. jhat/MAT:分析堆快照,定位内存泄漏。

最后小结

核心回顾

  1. jps:列出 JVM 进程 ID,是所有工具的前置步骤;
  2. jstat :监控 GC / 类加载状态,核心参数-gc/-gcutil,定位 GC 频繁问题;
  3. jmap :生成堆快照(-dump)、查看堆布局(-heap),定位内存泄漏;
  4. jstack :生成线程快照(-l),排查死锁 / 线程阻塞,核心看DEADLOCK/BLOCKED
  5. jhat:分析堆快照,提供 HTTP 界面,适合新手入门(生产环境推荐 MAT)。
相关推荐
猿小羽2 小时前
Java 架构演进史:从咖啡杯到云原生霸主
java·云原生·架构
Java程序员威哥2 小时前
使用Java自动加载OpenCV来调用YOLO模型检测
java·开发语言·人工智能·python·opencv·yolo·c#
小北方城市网2 小时前
Spring Cloud Gateway实战:路由、限流、熔断与鉴权全解析
java·spring boot·后端·spring·mybatis
ZealSinger2 小时前
Nacos2.x 事件驱动架构:原理与实战
java·spring boot·spring·spring cloud·nacos·架构·事件驱动
独行soc3 小时前
2026年渗透测试面试题总结-7(题目+回答)
java·网络·python·安全·web安全·渗透测试·安全狮
007php0074 小时前
PHP与Java项目在服务器上的对接准备与过程
java·服务器·开发语言·分布式·面试·职场和发展·php
sheji34164 小时前
【开题答辩全过程】以 民宿预订管理系统的设计与实现为例,包含答辩的问题和答案
java
刘大猫.5 小时前
XNMS项目-拓扑图展示
java·人工智能·算法·拓扑·拓扑图·节点树·xnms
正在努力Coding10 小时前
SpringAI - 工具调用
java·spring·ai