JVM性能监控与故障排查工具详解(jps、jstat、jmap、jstack、VisualVM、MAT、Arthas)

前言

JVM性能监控与故障排查工具是解决线上问题的利器。CPU突然飙升100%怎么排查?内存泄漏如何定位?死锁如何发现?线程一直WAITING在哪里? 理解jps的进程查看、jstat的实时监控、jmap的内存分析、jstack的线程快照、VisualVM的可视化监控,才能掌握线上问题快速定位的本质。

摘要

从"CPU 100%导致服务假死"的故障出发,剖析JVM性能监控与排查的核心工具。通过jstat的GC实时监控、jmap的堆内存dump、jstack的线程状态分析、MAT的内存泄漏定位、Arthas的在线诊断,揭秘从发现问题到定位根因的完整流程。配合详细命令示例与实战案例,给出线上问题排查的最佳实践。


一、从CPU 100%说起

周五下午5点,哈吉米的服务突然卡死:

场景1:CPU 100%,服务无响应

监控告警:
  CPU使用率:100%(4核全满)
  接口响应:超时
  用户投诉:打不开页面

哈吉米(慌):“什么情况?CPU打满了?”

登录服务器查看:

top

PID   USER  PR  NI  VIRT  RES  SHR  S %CPU %MEM    TIME+  COMMAND
12345 root  20   0  4.5g  2.3g  10m  R 398.7 58.2  10:23  java

# CPU 398.7%(4核 × 100%)

哈吉米:“Java进程CPU 400%,肯定是代码有问题。但是哪里有问题?”

南北绿豆赶来:“用jstack看线程在干什么。”

# 1. 找到CPU最高的线程
top -Hp 12345

PID    %CPU
12346  99.8  ← 这个线程CPU最高
12347  0.3
12348  0.1

# 2. 转为16进制(jstack用16进制显示线程ID)
printf "%x\n" 12346
# 输出:303a

# 3. jstack查看这个线程在干什么
jstack 12345 | grep 303a -A 30

"http-nio-8080-exec-1" #123 daemon prio=5 tid=0x303a
   java.lang.Thread.State: RUNNABLE
   at com.example.service.UserService.exportAllUsers(UserService.java:45)
   at com.example.controller.UserController.export(UserController.java:30)

南北绿豆:“找到了!UserService.exportAllUsers这个方法CPU 100%。”

查看代码:

public void exportAllUsers() {
    List<User> users = userMapper.selectAll();  // 查询500万用户
    
    StringBuilder sb = new StringBuilder();
    for (User user : users) {
        sb.append(user.toString());  // 字符串拼接500万次
        sb.append("\n");
    }
    
    return sb.toString();
}

// 问题:
// 500万次字符串拼接
// StringBuilder频繁扩容
// CPU 100%

哈吉米:“原来是这里!”

阿西噶阿西:“来,我教你完整的排查流程和工具使用。”


二、JVM命令行工具

2.1 jps(查看Java进程)

命令

jps

# 输出:
12345 Application
23456 jar
34567 Jps

# 详细信息
jps -l

12345 com.example.Application
23456 /opt/app/app.jar

# 查看JVM参数
jps -v

12345 Application -Xmx4g -Xms4g -XX:+UseG1GC

常用参数

参数说明
-l显示主类全名或jar路径
-v显示JVM参数
-m显示main方法参数

哈吉米:“jps就是Java版的ps,查看Java进程。”


2.2 jstat(实时监控)

监控GC

jstat -gc 12345 1000 10

# 每1秒输出一次,共10次

 S0C    S1C     S0U    S1U      EC       EU        OC         OU       MC      MU    
102400 102400  51200   0.0   819200   614400   2097152   1572864  51200   48000

YGCT   YGCT   FGC    FGCT     GCT
 150   15.234   5    25.123   40.357

字段说明:
S0C:Survivor0容量(KB)
S0U:Survivor0使用(KB)
EC:Eden容量
EU:Eden使用
OC:Old老年代容量
OU:Old老年代使用
YGCT:Young GC总耗时(秒)
FGCT:Full GC总耗时(秒)
GCT:GC总耗时

监控GC百分比

jstat -gcutil 12345 1000 10

  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00  50.00  75.00  75.00  93.75  90.00   150   15.234     5   25.123   40.357
  0.00  50.00  80.00  80.00  93.75  90.00   150   15.234     5   25.123   40.357
  0.00  50.00  90.00  90.00  93.75  90.00   150   15.234     5   25.123   40.357
  0.00  50.00  95.00  95.00  93.75  90.00   151   15.456     5   25.123   40.579
                ↑ Eden使用率上升                  ↑ YGC +1

问题:
  Eden使用率持续上升 → 对象创建过快
  YGC频繁 → 年轻代空间不足

监控类加载

jstat -class 12345

Loaded  Bytes  Unloaded  Bytes     Time
 12345  23456      100    200     120.5

# Loaded:加载的类数量
# Unloaded:卸载的类数量(持续为0正常,持续增长异常)

常用参数

参数说明
-gcGC统计(容量和使用量)
-gcutilGC统计(百分比)
-gccauseGC统计 + GC原因
-class类加载统计

南北绿豆:“jstat是实时监控的利器,能快速发现问题。”


2.3 jmap(内存分析)

功能1:dump堆内存

# 导出堆快照(会触发Full GC)
jmap -dump:live,format=b,file=heap.hprof 12345

# 参数说明:
# live:只导出存活对象(触发Full GC)
# format=b:二进制格式
# file:输出文件

# 导出后用MAT分析
# 文件大小约为堆内存的60-80%

功能2:查看堆配置

jmap -heap 12345

# 输出:
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 4294967296 (4096.0MB)
   NewSize                  = 1431633920 (1365.3MB)
   MaxNewSize               = 1431633920 (1365.3MB)
   OldSize                  = 2863333376 (2730.7MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.8MB)
   
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 1073741824 (1024.0MB)
   used     = 858993459 (819.2MB)
   free     = 214748365 (204.8MB)
   80.0% used

功能3:查看对象统计

jmap -histo 12345 | head -20

# 输出:
 num     #instances         #bytes  class name
----------------------------------------------
   1:       1234567      123456789  [C  ← char数组
   2:       1000000      100000000  java.lang.String
   3:        500000       50000000  com.example.entity.User
   4:        300000       30000000  java.util.HashMap$Node
   5:        200000       20000000  byte[]

分析:
  User对象有50万个,占50MB
  如果不合理 → 可能泄漏

阿西噶阿西:“jmap能看到内存使用详情,定位内存泄漏。”


2.4 jstack(线程分析)

功能1:导出线程快照

jstack 12345 > thread.txt

# 查看所有线程状态
cat thread.txt

"http-nio-8080-exec-1" #12 daemon prio=5 tid=0x7f8a1c004e00 nid=0x303a waiting on condition
   java.lang.Thread.State: WAITING (parking)
   at sun.misc.Unsafe.park(Native Method)
   at java.util.concurrent.locks.LockSupport.park
   at com.example.service.UserService.method1(UserService.java:30)

线程状态

状态说明原因
RUNNABLE运行中正常
WAITING等待中wait()、park()
TIMED_WAITING限时等待sleep()、wait(timeout)
BLOCKED阻塞等待获取synchronized锁

功能2:检测死锁

jstack 12345

# 输出:
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x7f8a1c004e00 (object 0xd5f20000),
  which is held by "Thread-2"
"Thread-2":
  waiting to lock monitor 0x7f8a1c002e00 (object 0xd5f20010),
  which is held by "Thread-1"

功能3:找CPU高的线程

# 结合top使用
top -Hp 12345  # 找到CPU高的线程ID

# 转为16进制
printf "%x\n" 12346
# 303a

# jstack查看
jstack 12345 | grep 303a -A 30

南北绿豆:“jstack能看到线程在干什么,定位死锁、死循环、阻塞问题。”


2.5 jinfo(查看和修改JVM参数)

查看参数

# 查看所有JVM参数
jinfo -flags 12345

# 查看某个参数
jinfo -flag MaxHeapSize 12345
# 输出:-XX:MaxHeapSize=4294967296

# 查看系统属性
jinfo -sysprops 12345

动态修改参数

# 修改GC日志开关(部分参数支持动态修改)
jinfo -flag +PrintGC 12345  # 开启
jinfo -flag -PrintGC 12345  # 关闭

# 修改堆大小(不支持,需要重启)
jinfo -flag MaxHeapSize=8g 12345
# 报错:Cannot change MaxHeapSize

可动态修改的参数

参数是否可动态修改
PrintGC✅ 可以
HeapDumpOnOutOfMemoryError✅ 可以
MaxHeapSize❌ 不可以
UseG1GC❌ 不可以

哈吉米:“jinfo可以查看参数,部分参数还能动态修改。”


三、可视化监控工具

3.1 VisualVM(JDK自带)

启动

# JDK 8及以前自带
jvisualvm

# JDK 9后需要单独下载
# https://visualvm.github.io/

核心功能

1. 监控
   - 实时查看CPU、内存、类、线程
   - 图形化展示

2. 线程
   - 查看所有线程状态
   - 线程dump
   - 死锁检测

3. 抽样器
   - CPU抽样(找到耗CPU的方法)
   - 内存抽样(找到占内存的对象)

4. Profiler
   - CPU性能分析
   - 内存性能分析

监控面板示例

CPU使用率:
  ████████████████████░░ 80%

堆内存:
  已使用:2.3GB / 4GB
  ████████████████░░░░░░ 58%

类加载:
  已加载:12,345个
  
线程:
  活动线程:150个
  守护线程:50个

3.2 MAT(Memory Analyzer Tool)

分析堆快照

1. File → Open Heap Dump → 选择heap.hprof

2. 自动生成Leak Suspects Report(泄漏疑点报告)

3. 查看Histogram(对象统计)
   Class Name                    Objects    Shallow Heap
   -------------------------------------------------
   java.lang.String              1,000,000  24,000,000
   com.example.entity.User         500,000  16,000,000
   byte[]                          300,000  50,000,000

4. 查看Dominator Tree(支配树)
   找到占内存最多的对象

5. 查看GC Roots路径
   右键对象 → Path to GC Roots
   查看引用链

Leak Suspects报告示例

Problem Suspect 1:

The thread "http-nio-8080-exec-1" 
  occupies 1.2 GB (60% of total heap)

Details:
- com.example.cache.LocalCache.dataMap
  └─ HashMap with 500,000 entries
  └─ occupies 1.2 GB

Shortest Paths From GC Roots:
  LocalCache (Class) 
  → dataMap (static field)
  → HashMap
  → 500,000 User objects

Suggestion:
  LocalCache.dataMap缓存500万个User对象
  没有过期机制,导致内存泄漏
  建议:使用软引用 + LRU淘汰

南北绿豆:“MAT的Leak Suspects能自动分析泄漏,非常强大。”


3.3 Arthas(阿里开源)

启动Arthas

# 下载
curl -O https://arthas.aliyun.com/arthas-boot.jar

# 启动(选择进程)
java -jar arthas-boot.jar

[INFO] arthas-boot version: 3.6.7
[INFO] Found existing java process, please choose one and input the serial number.
* [1]: 12345 com.example.Application
  [2]: 23456 jar
1  # 输入1,attach到进程12345

核心功能

功能1:dashboard(实时监控)

dashboard

# 实时显示:
线程:150个
内存:heap: 2.3GB/4GB, nonheap: 128MB
GC:YoungGC: 150次, FullGC: 5次
CPU:80%

功能2:thread(查看线程)

# 查看所有线程
thread

# 查看CPU最高的3个线程
thread -n 3

# 查看某个线程的栈
thread 123

# 查看死锁
thread -b

功能3:jad(反编译)

# 反编译类
jad com.example.service.UserService

# 输出:
public class UserService {
    public void exportAllUsers() {
        // 反编译后的代码
        ...
    }
}

功能4:watch(方法监控)

# 监控方法执行
watch com.example.service.UserService exportAllUsers '{params, returnObj, throwExp}' -x 2

# 输出:
method=com.example.service.UserService.exportAllUsers
params=[]
returnObj=...
耗时:5234ms

功能5:trace(方法耗时分析)

trace com.example.service.UserService exportAllUsers

# 输出调用树:
`---ts=2024-10-31 15:30:45;thread_name=http-nio-8080-exec-1;id=1a;is_daemon=true;priority=5;
    `---[5234.567ms] com.example.service.UserService:exportAllUsers()
        +---[2.345ms] com.example.mapper.UserMapper:selectAll()
        +---[5230.123ms] java.lang.StringBuilder:append() [500万次]
        `---[2.099ms] java.lang.StringBuilder:toString()

# 发现:StringBuilder.append耗时5230ms(瓶颈)

阿西噶阿西:“Arthas能在线诊断,不用重启应用,非常方便。”


四、线上问题排查实战

4.1 CPU 100%排查

完整流程

发现CPU 100%
top找Java进程
top -Hp找线程
线程ID转16进制
jstack查看线程栈
定位代码位置
修复代码

步骤

# 1. top找进程
top
# PID 12345,CPU 400%

# 2. top -Hp找线程
top -Hp 12345
# 线程12346,CPU 99.8%

# 3. 转16进制
printf "%x\n" 12346
# 303a

# 4. jstack查看
jstack 12345 | grep 303a -A 30
# 找到UserService.exportAllUsers

# 5. 修复代码
# 优化字符串拼接,改用BufferedWriter

典型CPU高的原因

1. 死循环
   while(true) { ... }

2. 正则表达式回溯
   复杂正则匹配大字符串

3. 频繁GC
   Full GC耗CPU

4. 大量计算
   复杂算法、加密解密

4.2 内存泄漏排查

完整流程

graph TB
    Problem[发现内存持续上升] --> Jstat[jstat监控老年代]
    Jstat --> Growing{老年代<br/>持续增长?}
    
    Growing -->|是| Dump1[第1次dump]
    Dump1 --> Wait[等待1小时]
    Wait --> Dump2[第2次dump]
    
    Dump2 --> MAT[用MAT对比两次dump]
    MAT --> Find[找到一直增长的对象]
    Find --> Path[查看GC Roots路径]
    Path --> Fix[修复代码]
    
    Growing -->|否| Normal[正常,无泄漏]

对比分析

第1次dump(14:00):
  User对象:10万个,占10MB

第2次dump(15:00):
  User对象:50万个,占50MB

结论:
  1小时增长40万个User对象
  → User对象泄漏

4.3 死锁排查

完整流程

# 1. jstack导出线程快照
jstack 12345 > thread.txt

# 2. 查找"deadlock"
grep -A 20 "deadlock" thread.txt

# 输出:
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x7f8a1c004e00 (object 0xd5f20000),
  which is held by "Thread-2"
"Thread-2":
  waiting to lock monitor 0x7f8a1c002e00 (object 0xd5f20010),
  which is held by "Thread-1"

死锁代码示例

Object lockA = new Object();
Object lockB = new Object();

// 线程1
new Thread(() -> {
    synchronized (lockA) {
        Thread.sleep(100);
        synchronized (lockB) {  // 等待lockB
            doSomething();
        }
    }
}).start();

// 线程2
new Thread(() -> {
    synchronized (lockB) {
        Thread.sleep(100);
        synchronized (lockA) {  // 等待lockA
            doSomething();
        }
    }
}).start();

// 结果:死锁

哈吉米:“jstack能自动检测死锁,太方便了。”


五、监控工具总结

5.1 核心要点

南北绿豆总结:

  1. jps:查看Java进程
  2. jstat:实时监控GC、类加载、编译
  3. jmap:dump堆内存、查看堆配置、对象统计
  4. jstack:线程快照、死锁检测、找CPU高的线程
  5. jinfo:查看和修改JVM参数
  6. VisualVM:可视化监控,图形化
  7. MAT:内存泄漏分析,自动生成报告
  8. Arthas:在线诊断,功能最全

5.2 面试高频问题

阿西噶阿西

问题1:线上CPU 100%,如何排查?

排查步骤:
  
1. top找到Java进程PID
   top
   PID 12345, CPU 400%
   
2. top -Hp找到CPU高的线程
   top -Hp 12345
   线程12346, CPU 99.8%
   
3. 线程ID转16进制
   printf "%x\n" 12346
   输出:303a
   
4. jstack查看线程栈
   jstack 12345 | grep 303a -A 30
   找到具体方法
   
5. 定位代码,修复
   
常见原因:
- 死循环
- 正则回溯
- 频繁GC

问题2:线上OOM,如何排查?

排查步骤:
  
1. 配置自动dump
   -XX:+HeapDumpOnOutOfMemoryError
   -XX:HeapDumpPath=logs/heap.hprof
   
2. OOM时自动生成heap.hprof
   
3. 用MAT分析
   - 打开heap.hprof
   - 查看Leak Suspects(自动分析)
   - 查看Histogram(对象统计)
   - 查看Dominator Tree(占用最多的对象)
   
4. 找到泄漏对象
   - 查看GC Roots路径
   - 找到引用链
   
5. 修复代码

问题3:常用的JVM监控工具有哪些?

命令行工具:
  - jps:查看Java进程
  - jstat:实时监控(GC、内存)
  - jmap:内存分析(dump、统计)
  - jstack:线程分析(栈、死锁)
  - jinfo:查看参数
  
可视化工具:
  - VisualVM:JDK自带,监控 + 性能分析
  - MAT:Eclipse出品,内存泄漏分析
  - JProfiler:商业工具,功能全面
  - Arthas:阿里开源,在线诊断
  
选择:
  - 快速排查:Arthas
  - 内存分析:MAT
  - 综合监控:VisualVM

问题4:如何定位死锁?

步骤:
  
1. jstack导出线程快照
   jstack <pid> > thread.txt
   
2. 查找deadlock关键字
   grep "deadlock" thread.txt
   
3. 查看死锁线程
   jstack会自动分析,显示:
   - Thread-1持有锁A,等待锁B
   - Thread-2持有锁B,等待锁A
   
4. 定位代码
   根据线程栈,找到死锁的代码位置
   
5. 修复
   - 统一加锁顺序
   - 使用tryLock设置超时
   - 减少锁的范围

问题5:Arthas有哪些常用命令?

监控类:
  - dashboard:实时监控面板
  - thread:线程分析
  - jvm:JVM信息
  
诊断类:
  - watch:监控方法调用
  - trace:方法调用链路耗时
  - stack:方法调用堆栈
  - tt:方法调用时光隧道(录制回放)
  
查看类:
  - jad:反编译类
  - sc:查找类
  - sm:查找方法
  
修改类:
  - redefine:热更新类(不重启)

哈吉米:“掌握了这些工具,线上问题排查就有底气了。”


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值