Skip to content

06.2 - 常用工具(jps / jstat / jmap / jstack / jinfo)

定位: JDK自带的5个命令行诊断工具——每个生产服务器上都有,不需要额外安装面试高频度: ⭐⭐⭐⭐⭐

一、为什么是这五个工具?

生产服务器通常没有GUI,不能装Arthas,不能装MAT。但只要装了JDK,这五个工具就可用。它们是JVM诊断的最后一道防线。

诊断场景 → 工具映射:
  不知道有哪些Java进程 → jps
  怀疑GC有问题         → jstat
  想看内存分布         → jmap
  怀疑线程卡住         → jstack
  想查JVM参数         → jinfo

二、诊断工作流全景

                          ┌─────────────────┐
                          │   系统出问题了    │
                          └────────┬────────┘

                          ┌────────▼────────┐
                          │  jps -lvm       │
                          │  找到目标PID     │
                          └────────┬────────┘

                  ┌────────────────┼────────────────┐
                  ▼                ▼                ▼
          ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
          │ jstat -gc    │ │ top -H -p    │ │ jstack       │
          │ GC是否异常?  │ │ CPU最高的线程 │ │ 线程状态检查  │
          └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
                 │               │                 │
                 ▼               ▼                 ▼
          ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
          │ jmap -heap   │ │ jstack + TID │ │ jstack -l    │
          │ 内存总览      │ │ 定位代码     │ │ 检查死锁     │
          └──────┬───────┘ └──────────────┘ └──────────────┘

          ┌──────▼───────┐
          │ jmap -dump   │
          │ heap dump    │
          │ → MAT分析     │
          └──────────────┘

三、jps — JVM Process Status

WHY: 所有JVM诊断的第一步——你需要PID。

常用命令

bash
# 基础用法 - 显示PID和主类名
jps -l
# 输出示例:
# 12345 com.example.MyApplication
# 23456 org.apache.catalina.startup.Bootstrap

# 最常用组合 - 完整信息
jps -lvm
# 输出示例:
# 12345 com.example.MyApplication -Xms2g -Xmx2g -Dspring.profiles.active=prod
# 23456 org.apache.catalina.startup.Bootstrap -Dcatalina.base=/opt/tomcat
参数含义说明
-l显示完整主类名默认只显示简单类名
-v显示JVM参数诊断参数配置
-m显示main方法参数启动参数
-V不显示JVM参数精简输出

实战技巧

jps会连接JVM的/tmp/hsperfdata_<user>目录。如果进程重启后该目录没有更新,可能看到僵尸进程。遇到这种情况可以用ps aux | grep java替代。

四、jstat — JVM Statistics Monitoring

WHY: 最重要的监控工具。实时查看GC状态、内存使用率、类加载情况。随时检查GC是否异常。

4.1 jstat -gc — GC统计(最详细)

bash
jstat -gc <pid> 1000 3   # 每1秒采样,共3次

输出示例(带注释):

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    YGC   YGCT   FGC    FGCT   GCT
  --     --     --     --     --       --        --         --       --     --     --    --     --      --     --     ← 不存在的行(示例结构)
512.0  512.0  120.5   0.0   4608.0  2048.3   10240.0     5120.5    --     --     --    --     --      --     --

各列含义(必背):

含义诊断意义
S0CSurvivor 0 容量(KB)看Survivor是否过小
S1CSurvivor 1 容量(KB)与S0C对称
S0USurvivor 0 使用量(KB)活跃对象占Survivor比例
S1USurvivor 1 使用量(KB)谁是非空的Survivor
ECEden容量(KB)新生代分配
EUEden使用量(KB)当前Eden占用
OCOld容量(KB)老年代总大小
OUOld使用量(KB)重点指标—OU持续上涨说明可能有泄漏
MCMetaspace容量(KB)元空间已提交大小
MUMetaspace使用量(KB)MU接近MC说明需扩容
YGCYoung GC次数Minor GC频率
YGCTYoung GC总耗时(秒)每次YGC时间=YGCT/YGC
FGCFull GC次数重点指标—快速增长说明有问题
FGCTFull GC总耗时(秒)每次FGC时间=FGCT/FGC
GCTGC总耗时(秒)YGCT+FGCT

4.2 jstat -gcutil — GC使用率百分比(最直观)

bash
jstat -gcutil <pid> 1000

输出示例:

  S0     S1     E      O      M     CCS    YGC    YGCT    FGC   FGCT    GCT
 23.52   0.00  44.50  50.05  85.30  82.10  1256   4.567    3    0.345   4.912
  • S0=23.52%: Survivor 0 使用了 23.52%
  • E=44.50%: Eden 使用了 44.50%
  • O=50.05%: Old 使用了 50.05% ✅ 最需要关注的指标
  • M=85.30%: Metaspace 使用了 85.30%
  • YGC=1256: 发生了1256次Young GC
  • FGC=3: 发生了3次Full GC

当你在一台服务器上且只需要"GC是否正常"的快速判断时,jstat -gcutil是最好的选择。

4.3 jstat -gccause — 最近GC原因

bash
jstat -gccause <pid> 1000

输出示例:

  S0     S1     E      O      M     CCS    YGC    YGCT    FGC    FGCT    GCT    LGCC                 GCC
 12.5   0.00  30.0   60.0   80.0   75.0   2000   8.000    10    1.200   9.200   Allocation Failure   System.gc()
  • LGCC: 上次GC原因 (Last GC Cause)
  • GCC: 当前GC原因 (Current GC Cause)

如果LGCC显示System.gc(),说明有代码显式调用了System.gc()。此时应加-XX:+DisableExplicitGC

4.4 其他jstat命令

bash
jstat -gccapacity <pid>       # 各代容量信息
jstat -gcnew <pid>            # 新生代详细信息
jstat -gcold <pid>            # 老年代详细信息
jstat -gcmetacapacity <pid>   # 元空间容量
jstat -class <pid>            # 类加载统计(看加载/卸载数量)
jstat -compiler <pid>         # JIT编译统计

实战:快速GC健康检查

bash
# 检查Full GC是否异常
jstat -gcutil <pid> 1000 5

# 正常输出特征: FGC数不增长或缓慢增长
# 异常输出特征: FGC每几秒增长1次

# 检查老年代使用率趋势
OU=$(jstat -gc <pid> | awk '{print $16}')  # 不断上涨说明泄漏

五、jmap — Memory Map

WHY: 查看堆内存分布,dump堆用于深度分析。

5.1 堆信息

bash
# 堆概要(安全,不会触发GC)
jmap -heap <pid>

输出示例:

Attaching to process ID 12345, please wait...
Debugger attached successfully.

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 2147483648 (2048.0MB)
   NewSize                  = 734003200 (700.0MB)
   MaxNewSize               = 734003200 (700.0MB)
   OldSize                  = 1413480448 (1348.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor):
   capacity = 661127168 (630.5MB)
   used     = 330285056 (315.0MB)
   free     = 330842112 (315.5MB)
   50.0% used

Eden Space:
   capacity = 587202560 (560.0MB)
   used     = 293601280 (280.0MB)
   free     = 293601280 (280.0MB)
   50.0% used

From Space:
   capacity = 73924608 (70.5MB)
   used     = 36686848 (35.0MB)
   free     = 37237760 (35.5MB)
   49.7% used

Tenured Generation:
   capacity = 1413480448 (1348.0MB)
   used     = 1137734734 (1085.0MB)
   free     = 275745714 (263.0MB)
   80.5% used    ⚠️ 老年代使用率偏高!

5.2 类直方图

bash
# 安全模式 - 不触发Full GC
jmap -histo <pid>

# ⚠️ 生产慎用模式 - 先触发Full GC再统计
jmap -histo:live <pid>

输出示例(截取前5行):

 num     #instances         #bytes  class name
----------------------------------------------
   1:         50000      200000000  [B                ← byte[] 最大
   2:        120000       19200000  java.util.HashMap$Node
   3:         80000        6400000  java.lang.String
   4:        100000        3200000  java.lang.Object[]

⚠️ :live陷阱: jmap -histo:live会触发Full GC!在生产环境高峰期使用会引发STW。不加:live是安全的,只扫描已有数据。jmap -dump同理会触发Full GC。

5.3 堆Dump

bash
# ⚠️ 触发Full GC!生产慎用!
jmap -dump:format=b,file=/tmp/heap.hprof <pid>

# 不触发Full GC的方式(Linux)
gcore <pid>                           # 先core dump
jmap -dump:format=b,file=heap.hprof /usr/lib/jvm/java-8-openjdk-amd64/lib/amd64/server/libjvm.so <core_file>

正确做法: 在启动参数中预配置自动dump,而不是出事后再jmap -dump:

bash
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump/heap-$(date).hprof

六、jstack — Stack Trace

WHY: 查看线程状态,检测死锁,定位卡住的线程。

6.1 基础用法

bash
# 完整线程dump
jstack <pid>

# 包含锁信息
jstack -l <pid>

# 混合模式(包含C++帧)
jstack -m <pid>

6.2 线程状态解读

jstack输出中每个线程的状态:

状态含义排查方向
RUNNABLE正在执行或等待CPU正常 ✅
BLOCKED等待获取锁(被其他线程持有)🔴 锁竞争热点
WAITINGObject.wait() 无超时线程池正常等待任务
TIMED_WAITINGsleep/wait(timeout)/parkNanos线程池正常等待
TERMINATED线程已结束检查是否本应存活

6.3 线程dump示例(标注版)

"http-nio-8080-exec-18" #85 daemon prio=5 os_prio=0 tid=0x00007f3c900b8000 nid=0x2b17
   waiting for monitor entry [0x00007f3c8e5f4000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.example.service.OrderService.createOrder(OrderService.java:125)
    - waiting to lock <0x000000076b5c6a60> (a java.lang.Object)      ← 等待这个锁
    at com.example.controller.OrderController.create(OrderController.java:45)
    - locked <0x000000076b5c6a58> (a java.lang.Object)               ← 持有了另一个锁

"http-nio-8080-exec-17" #84 daemon prio=5 os_prio=0 tid=0x00007f3c900b6800 nid=0x2b16
   waiting for monitor entry [0x00007f3c8e6f5000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.example.service.InventoryService.deduct(InventoryService.java:78)
    - waiting to lock <0x000000076b5c6a58> (a java.lang.Object)      ← 等待锁A
    at com.example.controller.InventoryController.deduct(InventoryController.java:22)
    - locked <0x000000076b5c6a60> (a java.lang.Object)               ← 持有锁B

上面这组dump表明"exec-18持有锁A等待锁B,exec-17持有锁B等待锁A"——典型的死锁

6.4 死锁检测

bash
jstack -l <pid>

jstack会自动检测死锁并输出:

Found one Java-level deadlock:
=============================
"http-nio-8080-exec-18":
  waiting to lock monitor 0x00007f3c8e5f4000 (object 0x000000076b5c6a58, a java.lang.Object),
  which is held by "http-nio-8080-exec-17"
"http-nio-8080-exec-17":
  waiting to lock monitor 0x00007f3c8e6f5000 (object 0x000000076b5c6a60, a java.lang.Object),
  which is held by "http-nio-8080-exec-18"

Found 1 deadlock.

🔑 jstack会主动扫描线程间锁的依赖关系,如果形成环,就报告死锁。这是排查死锁最快的方式,没有之一。

6.5 排查CPU飙升的jstack用法

bash
# Step 1: 找到CPU最高的线程TID
top -H -p <pid>

# Step 2: 将TID转为十六进制
printf '%x\n' <tid>
# 输出: a2c

# Step 3: 在jstack中搜索
jstack <pid> | grep -A 30 '0xa2c'

详细方法论见 **CPU飙升排查方法论**。

七、jinfo — Configuration Info

WHY: 查看运行时JVM参数,甚至动态修改部分参数,不需要重启进程

7.1 查看参数

bash
# 查看所有非默认参数
jinfo -flags <pid>

# 查看单个参数
jinfo -flag MaxHeapSize <pid>
# 输出: -XX:MaxHeapSize=2147483648

# 查看是否开启
jinfo -flag PrintGCDetails <pid>
# 输出: -XX:+PrintGCDetails

7.2 动态修改参数

bash
# 开启GC日志(无需重启!)
jinfo -flag +PrintGCDetails <pid>
jinfo -flag +PrintGCDateStamps <pid>
jinfo -flag +PrintGCApplicationStoppedTime <pid>

# 修改参数值
jinfo -flag MaxMetaspaceSize=256m <pid>

⚠️ 并非所有参数都可动态修改。只有标记为manageable的参数才支持运行时修改。检查方式:

bash
java -XX:+PrintFlagsFinal -version | grep manageable

常见的manageable参数: PrintGCDetails, HeapDumpOnOutOfMemoryError, MaxMetaspaceSize, MaxHeapFreeRatio, MinHeapFreeRatio

八、综合实战:一次完整的排查过程

场景:服务响应变慢

bash
# Step 1: 找到PID
jps -lvm | grep my-app
# 输出: 12345 /opt/app/my-app.jar -Xms2g -Xmx2g

# Step 2: 检查GC情况
jstat -gcutil 12345 1000 5
# S0  S1   E    O    M   YGC   YGCT  FGC FGCT  GCT
# --  --   --   --   --  --    --    --  --    --
# 0.0 0.0 80.0 95.0 85.0 5000  30.0   50 25.0  55.0
#                                   ^^^^
# FGC=50且每次FGCT=0.5秒! 这说明Full GC频繁

# Step 3: 看堆内存分布
jamp -heap 12345
# 看到Old使用率95%,说明堆快满了

# Step 4: 排查内存泄漏嫌疑对象
jmap -histo 12345 | head -20
# 发现 byte[] 和 HashMap$Node 占用巨大

# Step 5: 如果怀疑线程卡住,检查线程状态
jstack -l 12345 | grep "java.lang.Thread.State" | sort | uniq -c
# 输出:
#  42 RUNNABLE
#  55 WAITING (on object monitor)
#   3 BLOCKED (on object monitor)  ← 少数阻塞,正常

九、面试视角

高频面试题

面试题考察点回答要点
"jstat的S0C、S1C、EC、OC是什么意思?"工具熟练度分别是Survivor0/Survivor1/Eden/Old的容量(KB)
"jmap dump会触发Full GC吗?"生产安全意识:live形式和dump都触发Full GC,生产需谨慎
"jstack如何检测死锁?"原理理解扫描锁依赖图,发现循环等待环则报告
"jinfo的-flag +和-flag -有什么区别?"动态调优+开启布尔参数,-关闭
"jps -lvm分别代表什么?"基础-l完整类名,-vJVM参数,-mmain参数
"jstat中OU一直上涨但FGC没有变化说明什么?"问题分析老年代使用量在增加但还没触发Full GC(可能还有空间,或未达到GC阈值),需要关注趋势
"jstack中BLOCKED和WAITING有什么区别?"Java并发基础BLOCKED是等待获取锁(进入synchronized块),WAITING是调用wait/join/park后主动等待通知

面试回答模板

Q: 线上CPU100%怎么排查? A: 四步走:top找PID → top -H -p &lt;pid&gt;找TID → printf '%x'转hex → jstack pid | grep -A 30定位。更快的方案是用Arthas的thread -n 3

Q: 排查过的JVM问题中最难的一次? A: (STAR法则) 某次服务OOM,jstat发现Old涨得快,jmap -histo发现byte[]巨大,jstack检查线程发现有大量未关闭的Socket连接,最终定位是网络库的连接池泄漏——连接对象不被GC回收因为线程池ThreadLocal持有引用。


📚 相关链接

  • ← 返回 **调优索引**
  • **CPU飙升排查方法论** - 使用jstack排查CPU问题
  • **OOM排查方法论** - 使用jmap和dump分析OOM
  • **FullGC频繁排查方法论** - 使用jstat诊断Full GC
  • **图形化工具(JVisualVM与Arthas)** - 更强大的工具
  • **Java堆** - 堆内存结构
  • **对象创建过程** - 对象分配基础

Knowledge4J — Java 知识库