{T}

线上排障方法论

线上排障方法论

为什么需要掌握 JVM 诊断工具

在生产环境中,Java 应用可能遇到各种棘手的问题:

常见线上问题类型

code
┌─────────────────────────────────────────────────────────────┐
│                    常见 JVM 线上问题                         │
├─────────────────────────────────────────────────────────────┤
│  1. CPU 飙高                                                 │
│     - 死循环、自旋等待                                        │
│     - 频繁 GC                                                │
│     - 密集计算任务                                            │
├─────────────────────────────────────────────────────────────┤
│  2. 内存问题                                                 │
│     - 内存泄漏(Memory Leak)                                │
│     - 内存溢出(OOM)                                        │
│     - 直接内存溢出                                            │
│     - Metaspace 溢出                                         │
├─────────────────────────────────────────────────────────────┤
│  3. GC 问题                                                  │
│     - 频繁 Full GC                                           │
│     - GC 停顿时间过长                                         │
│     - Young GC 过于频繁                                       │
├─────────────────────────────────────────────────────────────┤
│  4. 线程问题                                                 │
│     - 线程死锁                                                │
│     - 线程阻塞                                                │
│     - 线程泄漏(线程数持续增长)                               │
│     - 线程池耗尽                                              │
├─────────────────────────────────────────────────────────────┤
│  5. 类加载问题                                               │
│     - ClassNotFoundException                                 │
│     - ClassCastException                                     │
│     - 类冲突(jar 包冲突)                                    │
└─────────────────────────────────────────────────────────────┘

排障的核心原则

重要原则

正确顺序:现象 → 取证 → 根因

  1. 先看监控和现象,确定问题类型(CPU/内存/GC/线程)
  2. 再选合适工具取证,收集线程栈、堆快照、GC 日志等
  3. 最后回到代码定位根因,找到具体代码位置并修复

错误做法:

  • × 一出问题就抓 heap dump(可能不是内存问题)
  • × 还没确认问题类型就改 JVM 参数
  • × 不看监控直接分析工具输出
  • × 重启后现场丢失,无法定位根因

一、JDK 自带命令行工具

JDK 自带了一系列强大的命令行诊断工具,是排查线上问题的基础武器。这些工具位于 JDK 的 bin 目录下,可以直接使用。

1.1 工具选择速查表

code
┌────────────────────────────────────────────────────────────────────────────┐
│                        命令行诊断工具速查                                    │
├───────────┬──────────────────────┬─────────────────────────────────────────┤
│  工具     │  主要用途            │  常用场景                               │
├───────────┼──────────────────────┼─────────────────────────────────────────┤
│  jps      │ 查看 JVM 进程        │ 找到目标进程 ID                         │
├───────────┼──────────────────────┼─────────────────────────────────────────┤
│  jstat    │ 监控 GC 和内存       │ 实时监控 GC 频率、内存使用               │
├───────────┼──────────────────────┼─────────────────────────────────────────┤
│  jinfo    │ 查看和修改 JVM 参数  │ 查看参数配置、动态开启调试参数            │
├───────────┼──────────────────────┼─────────────────────────────────────────┤
│  jmap     │ 堆内存分析           │ 生成堆转储、查看对象统计                  │
├───────────┼──────────────────────┼─────────────────────────────────────────┤
│  jstack   │ 线程栈分析           │ CPU 高、死锁、线程阻塞                   │
├───────────┼──────────────────────┼─────────────────────────────────────────┤
│  jcmd     │ 多功能诊断入口       │ 推荐使用,整合多种功能                   │
└───────────┴──────────────────────┴─────────────────────────────────────────┘

1.2 jps:JVM 进程查看工具

作用: 列出正在运行的 JVM 进程,类似于 Linux 的 ps 命令,但只显示 Java 进程。

基本用法
bash
## 列出 JVM 进程(只显示主类名)
jps

## 输出示例:
## 12345 Bootstrap
## 12346 Jps
## 12347 org.gradle.launcher.daemon.bootstrap.GradleDaemon

## 显示完整主类名或 JAR 路径
jps -l

## 输出示例:
## 12345 /opt/tomcat/bin/bootstrap.jar
## 12346 sun.tools.jps.Jps
## 12347 org.gradle.launcher.daemon.bootstrap.GradleDaemon

## 显示 JVM 参数
jps -v

## 输出示例:
## 12345 Bootstrap -Xms2g -Xmx2g -XX:+UseG1GC -Djava.awt.headless=true
## 12346 Jps -Dapplication.home=/usr/lib/jvm/java-11-openjdk

## 显示主函数参数
jps -m

## 输出示例:
## 12345 Bootstrap start
## 12346 Jps -m
常用选项对比
选项说明示例输出
无选项只显示进程 ID 和主类简称12345 Bootstrap
-l显示完整主类名或 JAR 路径12345 /opt/tomcat/bin/bootstrap.jar
-v显示 JVM 启动参数12345 Bootstrap -Xms2g -Xmx2g
-m显示主函数参数12345 Bootstrap start
-q只显示进程 ID12345
实战技巧

技巧 1:批量监控所有 JVM 进程的 GC 状态

bash
## 一键查看所有 JVM 进程的 GC 状态
for pid in $(jps -q | grep -v Jps); do
    echo "=== PID: $pid ==="
    jstat -gcutil $pid 2>/dev/null || echo "无法连接进程"
done

技巧 2:查找特定应用的进程

bash
## 查找包含 "myapp" 的进程
jps -l | grep myapp

## 输出示例:
## 12345 com.example.myapp.Application

## 提取进程 ID
jps -l | grep myapp | awk '{print $1}'

技巧 3:远程监控(需要配置 RMI)

bash
## 远程查看 JVM 进程(需要在远程主机配置 JMX)
jps -l remotehost:port
注意事项
  • jps 只能查看当前用户的 Java 进程
  • 如果 jps 无输出,可能是因为进程已经退出或权限不足
  • jps 通过 Java 的 Attach API 获取进程信息,如果 Attach API 被禁用则无法使用

1.3 jstat:JVM 统计监控工具

作用: 监控 JVM 类加载、内存、垃圾收集、JIT 编译等运行数据。jstat 是排查 GC 问题的首选工具。

核心选项
bash
## GC 统计(最常用)
jstat -gc <pid>

## GC 汇总统计(百分比形式,更直观)
jstat -gcutil <pid>

## GC 原因统计
jstat -gccause <pid>

## 类加载统计
jstat -class <pid>

## JIT 编译统计
jstat -compiler <pid>

## 打印 GC 统计摘要
jstat -gcprint <pid>
jstat -gc 输出详解
bash
jstat -gc 12345

## 输出示例:
##  S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    YGC     YGCT    FGC    FGCT     GCT   
## 10240  10240  0.0    8192.0  81920    40960.0   163840     131072.0  51200  48000   15      0.156   3      0.234    0.390

参数说明:

code
新生代(Young Generation)
├── S0C: Survivor 0 区容量(KB)
├── S1C: Survivor 1 区容量(KB)
├── S0U: Survivor 0 区已用(KB)
├── S1U: Survivor 1 区已用(KB)
├── EC : Eden 区容量(KB)
└── EU : Eden 区已用(KB)

老年代(Old Generation)
├── OC : 老年代容量(KB)
└── OU : 老年代已用(KB)

元空间(Metaspace)
├── MC : 元空间容量(KB)
├── MU : 元空间已用(KB)
├── CCSC: 压缩类空间容量(KB)
└── CCSU: 压缩类空间已用(KB)

GC 统计
├── YGC : Young GC 次数
├── YGCT: Young GC 总时间(秒)
├── FGC : Full GC 次数
├── FGCT: Full GC 总时间(秒)
├── GCT : GC 总时间(秒)
└── LGCC: 上次 GC 原因(jstat -gccause 显示)
jstat -gcutil 实战监控
bash
## 每隔 1 秒输出一次,共输出 10 次
jstat -gcutil 12345 1000 10

## 输出示例:
##   S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
##   0.00  93.47  24.62  46.68  93.47  88.71    15    0.156     3    0.234    0.390
##   0.00  93.47  28.45  46.68  93.47  88.71    15    0.156     3    0.234    0.390
##   0.00  93.47  45.12  46.68  93.47  88.71    15    0.156     3    0.234    0.390
##  85.23   0.00   0.00  47.25  93.47  88.71    16    0.168     3    0.234    0.402
##  85.23   0.00  15.34  47.25  93.47  88.71    16    0.168     3    0.234    0.402

## 持续监控(不指定次数则一直输出)
jstat -gcutil 12345 1000
监控指标解读

正常模式:

code
特征:
- Eden 区使用率周期性增长(0% → 100% → 0%)
- Survivor 区使用率稳定
- 老年代使用率稳定或缓慢增长
- YGC 次数稳定增长,FGC 很少

判断标准:
- Young GC 频率:正常范围 1-10 次/秒
- Full GC 频率:正常范围 0-1 次/小时
- GC 时间占比:正常 < 5%

异常模式 1:老年代持续增长

code
问题:老年代持续增长,即将触发 Full GC

现象:
- O 列持续增长,接近 100%
- FGC 次数开始增加
- FGCT 时间增长

可能原因:
- 内存泄漏
- 大对象过多
- 缓存未清理
- 对象晋升过快

解决方案:
- 增大堆内存
- 优化代码减少对象创建
- 使用有界缓存
- 分析 heap dump 找出大对象

异常模式 2:频繁 Young GC

code
问题:每秒 10+ 次 Young GC

现象:
- E 列快速从 0 增长到 100
- YGC 次数快速增长
- YGCT 时间累积

可能原因:
- 新生代太小
- 对象创建过快
- Eden 区设置过小

解决方案:
- 增大新生代(-Xmn 或 -XX:NewRatio)
- 优化代码减少对象创建
- 使用对象池重用对象

异常模式 3:频繁 Full GC

code
问题:频繁 Full GC,系统停顿

现象:
- FGC 次数频繁增长
- FGCT 时间很长
- 系统卡顿

可能原因:
- 内存不足
- 内存泄漏
- 老年代空间不足
- Metaspace 空间不足

解决方案:
- 增大堆内存
- 排查内存泄漏
- 增大 Metaspace(-XX:MaxMetaspaceSize)
- 调整 GC 策略
jstat 实战案例

案例 1:监控 GC 趋势

bash
## 每秒输出一次,监控 1 分钟
jstat -gcutil 12345 1000 60 > gc_monitor.log

## 分析趋势
## 查看 YGC 频率
grep -o "YGC.*" gc_monitor.log | awk '{print $1, $2}' | tail -10

## 查看老年代增长趋势
grep -o "O.*" gc_monitor.log | awk '{print $1}' | tail -20

案例 2:检测内存泄漏

bash
## 每隔 10 秒输出一次,持续监控
jstat -gcutil 12345 10000 | tee memory_leak_check.log

## 观察老年代(第 4 列)是否持续增长
## 正常:老年代使用率在 GC 后会下降或保持稳定
## 异常:老年代使用率持续上升,GC 后不下降

1.4 jinfo:JVM 配置信息工具

作用: 实时查看和调整 JVM 配置参数。jinfo 可以查看所有 JVM 参数,也可以动态修改部分参数。

基本用法
bash
## 查看所有 JVM 参数
jinfo <pid>

## 输出示例:
## Java System Properties:
## #Mon Mar 30 00:00:00 CST 2026
## java.runtime.name=OpenJDK Runtime Environment
## java.vm.version=11.0.11+9
## ...
## 
## VM Flags:
## -XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1HeapRegionSize=1048576 ...
##
## VM Arguments:
## jvm_args: -Xms2g -Xmx2g -XX:+UseG1GC
## java_command: com.example.Application
查看特定参数
bash
## 查看特定参数
jinfo -flag MaxHeapSize <pid>
## 输出: -XX:MaxHeapSize=2147483648

jinfo -flag UseG1GC <pid>
## 输出: -XX:+UseG1GC

## 查看所有 Flags
jinfo -flags <pid>

## 输出示例:
## VM Flags:
## -XX:CICompilerCount=4 -XX:ConcGCThreads=2 ...
## -XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=2147483648 ...
动态修改参数
bash
## 动态开启 GC 日志
jinfo -flag +PrintGCDetails <pid>

## 动态关闭 GC 日志
jinfo -flag -PrintGCDetails <pid>

## 设置 HeapDumpOnOutOfMemoryError
jinfo -flag +HeapDumpOnOutOfMemoryError <pid>

## 设置 HeapDump 路径
jinfo -flag HeapDumpPath=/tmp/heap.hprof <pid>
动态修改参数

只有标记为 manageable 的参数才能在运行时修改。

查看可动态修改的参数:

bash
## 查看所有 manageable 参数
java -XX:+PrintFlagsFinal -version | grep manageable

## 输出示例:
##     intx CMSAbortablePrecleanWaitMillis            = 100               {manageable}
##     intx CMSTriggerInterval                        = -1                {manageable}
##     bool HeapDumpOnOutOfMemoryError               = false             {manageable}
##    ccstr HeapDumpPath                              =                   {manageable}
##    uintx MaxHeapFreeRatio                          = 70                {manageable}
##    uintx MinHeapFreeRatio                          = 40                {manageable}
##     bool PrintGC                                   = false             {manageable}
##     bool PrintGCDetails                            = false             {manageable}
##     bool PrintGCDateStamps                         = false             {manageable}

常见可动态修改的参数:

  • PrintGCDetails / PrintGC
  • HeapDumpOnOutOfMemoryError / HeapDumpPath
  • MaxHeapFreeRatio / MinHeapFreeRatio
实战技巧

技巧 1:快速查看堆内存配置

bash
## 查看堆内存相关参数
jinfo <pid> | grep -i heap

## 输出示例:
## MaxHeapSize = 2147483648 (2048.0MB)
## NewSize = 681573760 (650.0MB)
## MaxNewSize = 681573760 (650.0MB)

技巧 2:临时开启 GC 日志排查问题

bash
## 生产环境遇到 GC 问题,临时开启 GC 日志
jinfo -flag +PrintGCDetails <pid>
jinfo -flag +PrintGCDateStamps <pid>

## 观察 GC 日志输出(控制台或日志文件)
## ...

## 排查完毕后关闭
jinfo -flag -PrintGCDetails <pid>
jinfo -flag -PrintGCDateStamps <pid>

1.5 jmap:内存映射工具

作用: 生成堆转储快照(heap dump)、查询堆内存详细信息、对象统计。jmap 是排查内存问题的核心工具。

生成堆转储
bash
## 方式 1:使用 jmap 生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>

## 输出示例:
## Dumping heap to /tmp/heap.hprof ...
## Heap dump file created

## 方式 2:使用 jcmd 生成堆转储(推荐)
jcmd <pid> GC.heap_dump /tmp/heap.hprof

## 生成堆转储时只包含存活对象(先执行 Full GC)
jmap -dump:live,format=b,file=heap_live.hprof <pid>

## 方式 3:使用 Arthas 生成堆转储
## heapdump /tmp/heap.hprof
注意事项

生成堆转储时会暂停应用(STW,Stop-The-World),大堆可能需要几分钟。

建议:

  1. 确认是内存问题再抓 heap dump
  2. 确认磁盘空间充足(堆转储文件大小 ≈ 堆大小)
  3. 在业务低峰期抓取
  4. 使用 live 选项可以减小文件大小(会触发 Full GC)
  5. 最好提前配置 HeapDumpOnOutOfMemoryError,OOM 时自动生成

配置 OOM 时自动生成 heap dump:

bash
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/heapdump.hprof -jar app.jar
查看堆内存使用
bash
## 查看堆内存详细信息
jcmd <pid> GC.heap_info   # JDK 9+ 推荐
# 旧版(JDK 8):jmap -heap <pid>(JDK 9+ 已移除 -heap 选项)

## 输出示例(g1gc):
## garbage-first heap   total 2048M, used 512M
##  region size 4M, 495 young (1980M), 5 survivors (20M)
##  Metaspace       used 45M, capacity 48M
对象统计
bash
## 查看堆中对象统计(对象数量、大小)
jmap -histo <pid> | head -25

## 输出示例:
##  num     #instances         #bytes  class name
## ----------------------------------------------
##    1:           12345       98765432  [B
##    2:           23456       76543210  [Ljava.lang.Object;
##    3:           34567       54321098  java.lang.String
##    4:           12345       43210987  java.util.HashMap$Node
##    5:           23456       32109876  java.lang.Integer
##    6:           34567       21098765  [C
##    7:           12345       10987654  java.util.concurrent.ConcurrentHashMap$Node

## 只统计存活对象(会触发 Full GC)
jmap -histo:live <pid> | head -25

## 输出到文件
jmap -histo <pid> > histo.txt

对象统计解读:

code
class name 说明:
[B          : byte 数组
[C          : char 数组
[I          : int 数组
[J          : long 数组
[L<class>   : 对象数组,如 [Ljava.lang.Object;
<class>     : 普通对象,如 java.lang.String

分析技巧:
1. 关注 instances 数量异常大的对象
2. 关注 bytes 占用最大的对象
3. 关注自定义类是否对象数量过多
4. 对比多次 histo 结果,找出增长的对象
实战案例:排查内存泄漏

步骤 1:查看对象统计

bash
## 查看对象统计
jmap -histo:live 12345 | head -30

## 输出示例:
##  num     #instances         #bytes  class name
## ----------------------------------------------
##    1:          500000      120000000  com.example.model.UserSession
##    2:          500000       40000000  java.lang.String
##    3:           10000       20000000  [B

步骤 2:生成堆转储

bash
## 生成堆转储
jmap -dump:live,format=b,file=leak.hprof 12345

步骤 3:使用 MAT 分析

详见后续章节 "MAT 分析技巧"。

1.6 jstack:线程栈跟踪工具

作用: 生成线程快照(thread dump),定位线程停顿、死锁、阻塞等问题。jstack 是排查 CPU 高和线程问题的首选工具。

基本使用
bash
## 生成线程快照
jstack <pid>

## 保存到文件
jstack <pid> > thread_dump.txt

## 生成更详细的线程栈(包含锁信息)
jstack -l <pid> > thread_dump_detail.txt

## 强制生成线程栈(当进程无响应时)
jstack -F <pid> > thread_dump_force.txt
线程状态详解
code
┌──────────────────────────────────────────────────────────────────────┐
│                        Java 线程状态                                  │
├──────────────────────────────────────────────────────────────────────┤
│  1. RUNNABLE(可运行)                                                 │
│     线程正在运行或等待 CPU 时间片                                       │
│     常见场景:执行代码、网络 I/O、文件 I/O                              │
│                                                                      │
│  2. BLOCKED(阻塞)                                                    │
│     线程等待获取监视器锁(synchronized)                                │
│     - 等待进入 synchronized 块                                        │
│     - 等待获取对象锁                                                   │
│                                                                      │
│  3. WAITING(无限等待)                                                │
│     线程等待其他线程唤醒,不会自动唤醒                                   │
│     - Object.wait()                                                  │
│     - Thread.join()                                                  │
│     - LockSupport.park()                                             │
│                                                                      │
│  4. TIMED_WAITING(计时等待)                                         │
│     线程等待指定时间后自动唤醒                                          │
│     - Thread.sleep()                                                 │
│     - Object.wait(long)                                              │
│     - Thread.join(long)                                              │
│     - LockSupport.parkNanos()                                        │
│     - LockSupport.parkUntil()                                        │
│                                                                      │
│  5. NEW(新建)                                                        │
│     线程已创建但未启动                                                 │
│                                                                      │
│  6. TERMINATED(终止)                                                │
│     线程已执行完毕                                                     │
└──────────────────────────────────────────────────────────────────────┘
线程状态示例

示例 1:RUNNABLE 状态(正常)

code
"http-nio-8080-exec-1" #10 daemon prio=5 os_prio=0 tid=0x00007f8c0c0a4800 nid=0x1234 runnable [0x00007f8bfe7f6000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:29)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:386)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
        at java.lang.Thread.run(Thread.java:748)

解读:线程正在执行网络读取操作,状态为 RUNNABLE,属于正常状态。

示例 2:BLOCKED 状态(锁竞争)

code
"thread-1" #15 prio=5 os_prio=0 tid=0x00007f8c0c0b5000 nid=0x1235 waiting for monitor entry [0x00007f8bfe5f4000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.example.Demo.method(Demo.java:25)
        - waiting to lock <0x00000000c0001234> (a java.lang.Object)
        - locked <0x00000000c0005678> (a java.lang.Object)
        at com.example.Demo.run(Demo.java:10)

解读:线程等待获取锁 <0x00000000c0001234>,当前被其他线程持有。
     线程已经持有锁 <0x00000000c0005678>。
     可能存在锁竞争或死锁。

示例 3:WAITING 状态(等待唤醒)

code
"thread-2" #16 prio=5 os_prio=0 tid=0x00007f8c0c0b6000 nid=0x1236 in Object.wait() [0x00007f8bfe4f2000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000c0001234> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at com.example.Demo.consume(Demo.java:30)
        at com.example.Demo.run(Demo.java:15)

解读:线程调用了 Object.wait(),等待其他线程 notify/notifyAll。
     典型的生产者-消费者模式。

示例 4:TIMED_WAITING 状态(睡眠)

code
"thread-3" #17 prio=5 os_prio=0 tid=0x00007f8c0c0b7000 nid=0x1237 waiting on condition [0x00007f8bfe3f0000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.example.Demo.run(Demo.java:20)

解读:线程调用了 Thread.sleep(),将在指定时间后自动唤醒。
检测死锁
bash
## jstack 会自动检测并打印死锁信息
jstack <pid> | grep -A 10 "Found one Java-level deadlock"

## 输出示例:
## Found one Java-level deadlock:
## =============================
## "thread-2":
##   waiting to lock monitor 0x00007f8c0c00a800 (object 0x00000000c0001234),
##   which is held by "thread-1"
## "thread-1":
##   waiting to lock monitor 0x00007f8c0c00b800 (object 0x00000000c0005678),
##   which is held by "thread-2"
## 
## Java stack information for the threads listed above:
## ===================================================
## "thread-2":
##         at com.example.DeadlockDemo.method2(DeadlockDemo.java:30)
##         - waiting to lock <0x00000000c0001234> (a java.lang.Object)
##         - locked <0x00000000c0005678> (a java.lang.Object)
##         at com.example.DeadlockDemo.run(DeadlockDemo.java:15)
## "thread-1":
##         at com.example.DeadlockDemo.method1(DeadlockDemo.java:20)
##         - waiting to lock <0x00000000c0005678> (a java.lang.Object)
##         - locked <0x00000000c0001234> (a java.lang.Object)
##         at com.example.DeadlockDemo.run(DeadlockDemo.java:10)
## 
## Found 1 deadlock.
分析线程栈技巧

技巧 1:统计线程状态

bash
## 统计各种状态的线程数量
jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c

## 输出示例:
##       5    java.lang.Thread.State: BLOCKED
##      10    java.lang.Thread.State: RUNNABLE
##      15    java.lang.Thread.State: TIMED_WAITING (sleeping)
##      20    java.lang.Thread.State: WAITING (on object monitor)

技巧 2:查找 CPU 高的线程

bash
## 1. 找到高 CPU 进程
top

## 输出示例:
##   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
##  12345 appuser   20   0  4.0g   1.2g   120m S 95.0  7.5   0:10.23 java

## 2. 找到高 CPU 线程
top -Hp 12345

## 输出示例:
##   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
##  12346 appuser   20   0  4.0g   1.2g   120m R 92.0  7.5   0:09.15 java
##  12347 appuser   20   0  4.0g   1.2g   120m S  1.0  7.5   0:00.10 java

## 3. 转换线程 ID 为十六进制
printf "%x\n" 12346
## 输出: 303a

## 4. 在线程栈中查找
jstack 12345 | grep -A 20 "nid=0x303a"

## 输出示例:
## "http-nio-8080-exec-10" #123 daemon prio=5 os_prio=0 tid=0x00007f8c0c0a4800 nid=0x303a runnable
##    java.lang.Thread.State: RUNNABLE
##         at com.example.service.CalculateService.calculate(CalculateService.java:25)
##         at com.example.controller.UserController.getUser(UserController.java:50)
##         ...

## 5. 定位到问题代码
## 查看 CalculateService.java 第 25 行代码

技巧 3:查找 BLOCKED 线程

bash
## 查找所有 BLOCKED 状态的线程
jstack <pid> | grep -B 1 "BLOCKED"

## 查找等待锁的线程
jstack <pid> | grep -A 5 "waiting to lock"

## 输出示例:
##         at com.example.Demo.method(Demo.java:25)
##         - waiting to lock <0x00000000c0001234> (a java.lang.Object)
##         - locked <0x00000000c0005678> (a java.lang.Object)

技巧 4:一键生成 CPU 高排查脚本

bash
#!/bin/bash
## cpu_high_check.sh

PID=$1

if [ -z "$PID" ]; then
    echo "Usage: $0 <pid>"
    exit 1
fi

echo "=== 高 CPU 线程分析 ==="

## 获取 CPU 最高的 3 个线程
echo "Top 3 高 CPU 线程:"
top -Hp $PID -n 1 -b | head -n 10 | tail -n 4

## 分析每个高 CPU 线程
for TID in $(top -Hp $PID -n 1 -b | head -n 10 | tail -n 3 | awk '{print $1}'); do
    HEX_TID=$(printf "%x\n" $TID)
    echo ""
    echo "=== 线程 $TID (0x$HEX_TID) ==="
    jstack $PID | grep -A 10 "nid=0x$HEX_TID"
done

echo ""
echo "=== 线程状态统计 ==="
jstack $PID | grep "java.lang.Thread.State" | sort | uniq -c | sort -rn

使用方法:

bash
chmod +x cpu_high_check.sh
./cpu_high_check.sh 12345

1.7 jcmd:多功能诊断工具

作用: jcmd 是一个统一的命令入口,整合了 jstack、jmap 等多种功能。jcmd 是 JDK 推荐的多功能诊断工具。

基本使用
bash
## 列出所有 JVM 进程
jcmd -l

## 输出示例:
## 12345 com.example.Application
## 12346 org.gradle.launcher.daemon.bootstrap.GradleDaemon
## 12347 sun.tools.jcmd.JCmd

## 查看特定进程支持的命令
jcmd <pid> help

## 输出示例:
## The following commands are available:
## VM.version
## VM.command_line
## VM.flags
## VM.system_properties
## VM.utilities
## Thread.print
## GC.heap_dump
## GC.class_histogram
## GC.run
## ...
常用命令

1. 查看 JVM 信息

bash
## 查看 JVM 版本信息
jcmd <pid> VM.version

## 输出示例:
## OpenJDK 64-Bit Server VM version 11.0.11+9
## JDK 11.0.11

## 查看 JVM 启动参数
jcmd <pid> VM.command_line

## 输出示例:
## JVM Arguments:
## jvm_args: -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
## java_command: com.example.Application
## java_class_path (initial): /app/classes:/app/lib/*

## 查看 JVM Flags
jcmd <pid> VM.flags

## 输出示例:
## -XX:CICompilerCount=4 -XX:ConcGCThreads=2 -XX:G1HeapRegionSize=1048576 ...

2. 线程相关

bash
## 打印线程栈(等同于 jstack)
jcmd <pid> Thread.print

## 打印线程栈(包含锁信息)
jcmd <pid> Thread.print -l

## 输出到文件
jcmd <pid> Thread.print > thread_dump.txt

3. 堆内存相关

bash
## 生成堆转储(等同于 jmap -dump)
jcmd <pid> GC.heap_dump /tmp/heap.hprof

## 输出示例:
## Heap dump file created

## 查看类统计(等同于 jmap -histo)
jcmd <pid> GC.class_histogram

## 输出示例:
##  num     #instances         #bytes  class name
## ----------------------------------------------
##    1:           12345       98765432  [B
##    2:           23456       76543210  [Ljava.lang.Object;
##    3:           34567       54321098  java.lang.String

## 执行 GC
jcmd <pid> GC.run

## 查看 GC 堆信息
jcmd <pid> GC.heap_info

4. 系统属性

bash
## 查看系统属性
jcmd <pid> VM.system_properties

## 输出示例:
## #Mon Mar 30 00:00:00 CST 2026
## java.runtime.name=OpenJDK Runtime Environment
## java.vm.version=11.0.11+9
## java.vm.vendor=Oracle Corporation
## ...

5. JVM 信息汇总

bash
## 查看所有 JVM 信息
jcmd <pid> VM.info

## 输出示例:
## VM Arguments:
## jvm_args: -Xms2g -Xmx2g -XX:+UseG1GC
## java_command: com.example.Application
## ...
jcmd vs 传统工具对比
code
┌────────────────────────────────────────────────────────────────────────────┐
│                        jcmd vs 传统工具对比                                  │
├────────────────────────────────────────────────────────────────────────────┤
│  功能                │  传统工具           │  jcmd 命令                     │
├────────────────────────────────────────────────────────────────────────────┤
│  查看进程            │  jps                │  jcmd -l                       │
│  线程栈              │  jstack <pid>       │  jcmd <pid> Thread.print       │
│  堆转储              │  jmap -dump         │  jcmd <pid> GC.heap_dump       │
│  类统计              │  jmap -histo        │  jcmd <pid> GC.class_histogram │
│  JVM 参数            │  jinfo              │  jcmd <pid> VM.flags           │
│  启动参数            │  jinfo              │  jcmd <pid> VM.command_line    │
│  执行 GC             │  无                 │  jcmd <pid> GC.run             │
└────────────────────────────────────────────────────────────────────────────┘

优势:
- jcmd 是统一入口,无需记忆多个工具
- jcmd 功能更强大,支持更多诊断命令
- jcmd 性能更好,开销更小
- jcmd 是 Oracle 推荐的诊断工具

二、可视化诊断工具

2.1 JConsole:Java 监控与管理控制台

作用: 提供图形化界面监控 JVM 的内存、线程、类、CPU 等信息。JConsole 是最基础的可视化监控工具。

启动方式
bash
## JDK 8 及以下版本自带
jconsole

## 连接方式:
## 1. 本地连接:选择本地进程,直接连接
## 2. 远程连接:需要配置 JMX
远程连接配置

方式 1:启动时配置 JMX

bash
## 启动应用时添加 JMX 参数
java \
  -Dcom.sun.management.jmxremote \
  -Dcom.sun.management.jmxremote.port=9010 \
  -Dcom.sun.management.jmxremote.authenticate=false \
  -Dcom.sun.management.jmxremote.ssl=false \
  -Djava.rmi.server.hostname=192.168.1.100 \
  -jar app.jar

## 然后在 JConsole 中连接:192.168.1.100:9010

方式 2:使用认证(生产环境推荐)

bash
## 1. 创建密码文件
cat > /opt/jmxremote.password <<EOF
monitorRole readonly123
controlRole readwrite123
EOF

## 2. 设置权限
chmod 600 /opt/jmxremote.password

## 3. 启动应用
java \
  -Dcom.sun.management.jmxremote \
  -Dcom.sun.management.jmxremote.port=9010 \
  -Dcom.sun.management.jmxremote.authenticate=true \
  -Dcom.sun.management.jmxremote.password.file=/opt/jmxremote.password \
  -Dcom.sun.management.jmxremote.ssl=false \
  -Djava.rmi.server.hostname=192.168.1.100 \
  -jar app.jar

## 4. 在 JConsole 中使用用户名密码连接
## 用户名: controlRole
## 密码: readwrite123
主要功能

1. 概览(Overview)

code
显示:
- 堆内存使用趋势图
- 线程数量变化图
- 类加载数量图
- CPU 使用率图

用途:快速了解应用整体运行状态

2. 内存(Memory)

code
功能:
- 查看各内存区域使用情况
- 手动触发 GC(点击 "Perform GC")
- 查看内存池详情

内存区域:
- Heap Memory Usage:堆内存使用
- Non-Heap Memory Usage:非堆内存使用
- Memory Pool "Eden Space":Eden 区
- Memory Pool "Survivor Space":Survivor 区
- Memory Pool "Tenured Gen":老年代
- Memory Pool "Metaspace":元空间

使用技巧:
1. 观察 "Heap Memory Usage" 图表,看内存是否持续增长
2. 点击 "Perform GC" 观察内存是否能回收
3. 如果 GC 后内存不下降,可能存在内存泄漏

3. 线程(Threads)

code
功能:
- 查看线程数量变化
- 查看线程列表
- 检测死锁(点击 "Detect Deadlock")
- 查看线程栈

使用技巧:
1. 观察线程数量是否持续增长(线程泄漏)
2. 点击 "Detect Deadlock" 检测死锁
3. 选中线程查看线程栈,定位问题

4. 类(Classes)

code
功能:
- 查看类加载数量
- 查看类加载/卸载统计

使用技巧:
1. 观察类加载数量是否持续增长(类泄漏)
2. 如果类加载过多,可能导致 Metaspace 溢出

5. VM 概要(VM Summary)

code
显示:
- JVM 版本
- JVM 参数
- 系统属性
- 堆内存配置
- GC 策略

用途:快速查看 JVM 配置信息
JConsole 实战案例

案例 1:监控内存泄漏

bash
## 1. 启动 JConsole 连接应用
jconsole

## 2. 切换到 "内存" 标签
## 3. 观察 "Heap Memory Usage" 图表

## 正常模式:
## - 内存使用呈锯齿状(上升 → GC → 下降)
## - GC 后内存使用率下降明显

## 内存泄漏模式:
## - 内存使用持续上升
## - GC 后内存使用率不下降
## - 老年代使用率接近 100%

## 4. 如果发现内存泄漏,使用 jmap 生成 heap dump 分析

案例 2:检测死锁

bash
## 1. 启动 JConsole 连接应用
jconsole

## 2. 切换到 "线程" 标签
## 3. 点击 "检测死锁" 按钮

## 如果发现死锁:
## Found deadlock!
## Thread-1 waiting to lock ...
## Thread-2 waiting to lock ...

## 4. 查看死锁线程栈,定位代码位置

2.2 VisualVM:多合一故障处理工具

作用: VisualVM 是最强大的 JDK 自带工具,集成了监控、分析、调优等多种功能。VisualVM 是排查问题的利器。

安装和启动
bash
## JDK 8 及以下版本自带
jvisualvm

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

## 下载后解压运行
./visualvm/bin/visualvm
主要功能

1. 监控(Monitor)

code
图表:
- CPU 使用率
- 堆内存使用
- 类加载数量
- 线程数量

操作:
- 执行 GC:触发垃圾回收
- Heap Dump:生成堆转储

使用技巧:
1. 观察 CPU 和内存趋势
2. 点击 "Heap Dump" 生成堆转储分析

2. 线程(Threads)

code
功能:
- 实时线程状态
- 线程时间线
- 线程 Dump

线程状态颜色:
- 绿色:Runnable
- 紫色:Sleeping
- 黄色:Wait
- 红色:Blocked

使用技巧:
1. 观察线程时间线,找出异常线程
2. 点击 "Thread Dump" 查看线程栈
3. 红色(Blocked)线程过多,说明锁竞争严重

3. 抽样器(Sampler)

code
功能:
- CPU 抽样:分析方法执行时间
- 内存抽样:分析对象分配

操作:
1. 选择 CPU 或内存抽样
2. 点击 "Start"
3. 运行一段时间后点击 "Stop"
4. 查看抽样结果

使用技巧:
- CPU 抽样:找出耗时最长的方法
- 内存抽样:找出创建对象最多的代码

4. 分析器(Profiler)

code
功能:
- CPU 分析:分析方法执行时间和调用次数
- 内存分析:分析对象创建和回收

注意:
- Profiler 开销较大,会影响性能
- 不适合在生产环境使用
- 建议在测试环境使用

使用技巧:
1. 选择 CPU 或内存分析
2. 点击 "Start"
3. 执行业务操作
4. 点击 "Stop" 查看结果
堆转储分析

生成堆转储:

code
方式 1:在应用程序面板右键 → Heap Dump
方式 2:监控面板 → Heap Dump 按钮
方式 3:打开已有的 .hprof 文件

堆转储分析功能:

code
1. 概要(Summary)
   - 堆大小
   - 对象数量
   - 类数量
   - GC Root 数量

2. 类(Classes)
   - 类名
   - 对象数量
   - 对象大小

3. 对象(Objects)
   - 对象列表
   - 对象详情
   - 引用关系
   - 查看 GC Roots

使用技巧:
1. 切换到 "类" 标签,按大小排序
2. 找出占用内存最大的类
3. 双击类名查看对象列表
4. 右键对象 → "最近 GC Root" 查看引用链
VisualVM 实战案例

案例 1:分析内存泄漏

bash
## 1. 启动 VisualVM 连接应用
jvisualvm

## 2. 点击 "Heap Dump" 生成堆转储
## 3. 切换到 "类" 标签,按大小排序

## 输出示例:
## Class               | Instances | Size
## ------------------- | --------- | ----
## byte[]              | 50000     | 100 MB
## java.lang.String    | 100000    | 50 MB
## UserSession         | 50000     | 80 MB  # 异常!

## 4. 双击 UserSession 类,查看对象列表
## 5. 右键对象 → "最近 GC Root",查看引用链

## 输出示例:
## GC Root: static field SessionManager.sessions
##   └── HashMap$Node
##       └── UserSession

## 6. 定位到 SessionManager.sessions 字段,发现缓存未清理

案例 2:分析 CPU 高

bash
## 1. 启动 VisualVM 连接应用
jvisualvm

## 2. 切换到 "抽样器" 标签
## 3. 选择 "CPU",点击 "Start"
## 4. 运行一段时间后点击 "Stop"

## 输出示例:
## Method                          | Time (ms) | Invocations
## ------------------------------- | --------- | -----------
## CalculateService.calculate()    | 5000      | 100000
## UserService.getUser()           | 2000      | 50000
## CacheManager.get()              | 1500      | 80000

## 5. 发现 CalculateService.calculate() 耗时最长
## 6. 查看代码发现死循环或复杂计算

2.3 JMC(Java Mission Control):低开销监控工具

作用: JMC 是 Oracle 官方推荐的低开销监控工具,适合生产环境使用。JMC 使用 JFR(Java Flight Recorder)技术,开销 < 1%。

安装和启动
bash
## JDK 8 自带(商业版)
## JDK 11+ 需要单独下载
## https://www.oracle.com/java/technologies/jdk-mission-control.html

## 下载后解压运行
./jmc/jmc

## 启动应用时开启 JFR
java \
  -XX:+UnlockCommercialFeatures \
  -XX:+FlightRecorder \
  -jar app.jar
JFR(Java Flight Recorder)

特点:

code
优势:
- 开销极低(< 1%)
- 适合生产环境
- 记录全面(CPU、内存、GC、线程、I/O 等)
- 支持事后分析

劣势:
- JDK 8 需要商业许可
- JDK 11+ 开源免费
- 需要学习成本
使用 JFR 记录

方式 1:启动时记录

bash
## 启动时开始记录
java \
  -XX:+UnlockCommercialFeatures \
  -XX:+FlightRecorder \
  -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr \
  -jar app.jar

## 记录 60 秒后保存到 myrecording.jfr

方式 2:使用 jcmd 动态记录

bash
## 启动应用(开启 JFR)
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -jar app.jar

## 查看可用记录配置
jcmd <pid> JFR.check

## 输出示例:
## Recording 1: name=1 maxsize=100.0MB maxage=1d

## 启动记录
jcmd <pid> JFR.start name=my_recording duration=60s filename=myrecording.jfr

## 输出示例:
## Started recording "my_recording". The result will be written to:
## /path/to/myrecording.jfr

## 停止记录
jcmd <pid> JFR.stop name=my_recording

## 导出记录
jcmd <pid> JFR.dump name=my_recording filename=myrecording.jfr

方式 3:使用 JMC GUI

code
1. 启动 JMC
2. 连接到 JVM 进程
3. 点击 "Start Flight Recording"
4. 选择记录配置和时长
5. 停止后自动打开分析界面
JFR 分析

分析内容:

code
1. 概览(Overview)
   - CPU 使用率
   - 堆内存使用
   - GC 统计
   - 线程统计

2. 内存(Memory)
   - 内存分配热点
   - GC 统计
   - 内存泄漏检测

3. 代码(Code)
   - 方法执行时间
   - 方法调用次数
   - 异常统计

4. 线程(Threads)
   - 线程状态
   - 锁竞争
   - 死锁检测

5. I/O
   - 文件 I/O
   - 网络 I/O
   - I/O 热点

6. 系统(System)
   - CPU 使用
   - 系统负载
   - 进程信息
JMC 实战案例

案例 1:分析性能瓶颈

bash
## 1. 启动 JFR 记录
jcmd 12345 JFR.start name=perf duration=60s filename=perf.jfr

## 2. 执行业务操作
## ...

## 3. 等待记录完成

## 4. 使用 JMC 打开 perf.jfr
jmc perf.jfr

## 5. 切换到 "代码" 标签,查看方法执行时间

## 输出示例:
## Method                      | Time (ms) | Samples
## --------------------------- | --------- | -------
## UserService.getUser()       | 5000      | 1000
## CacheManager.get()          | 3000      | 2000
## DatabaseService.query()     | 2000      | 500

## 6. 发现 UserService.getUser() 耗时最长,定位优化

2.4 图形化工具对比

code
┌────────────────────────────────────────────────────────────────────────────┐
│                        图形化诊断工具对比                                    │
├─────────────┬────────────────┬───────────────────────┬────────────────────┤
│  工具       │  开销          │  主要功能             │  适用场景           │
├─────────────┼────────────────┼───────────────────────┼────────────────────┤
│  JConsole   │ 低(< 2%)     │ 基础监控              │ 快速查看 JVM 状态   │
│             │                │ 内存、线程、类        │ 开发、测试环境      │
├─────────────┼────────────────┼───────────────────────┼────────────────────┤
│  VisualVM   │ 中(抽样器高) │ 监控、分析、调优      │ 通用诊断工具        │
│             │                │ CPU/内存分析         │ 测试环境、生产监控  │
├─────────────┼────────────────┼───────────────────────┼────────────────────┤
│  JMC/JFR    │ 极低(< 1%)   │ 低开销监控、事件分析  │ 生产环境监控        │
│             │                │ 全方位性能分析        │ 性能调优            │
├─────────────┼────────────────┼───────────────────────┼────────────────────┤
│  VisualGC   │ 低             │ GC 可视化             │ GC 分析            │
│             │                │ 垃圾回收可视化        │ GC 调优            │
└─────────────┴────────────────┴───────────────────────┴────────────────────┘

选择建议:
- 开发/测试环境:VisualVM(功能最全)
- 生产环境监控:JMC/JFR(开销最低)
- 快速查看:JConsole(最简单)
- GC 分析:VisualGC(专门分析 GC)

三、现代诊断工具:Arthas

Arthas 是阿里巴巴开源的 Java 诊断工具,功能强大。Arthas 是线上排查问题的重要工具。

3.1 Arthas 简介

特点:

code
优势:
- 无需重启应用:在线诊断
- 功能丰富:查看类、方法、线程、GC 等
- 交互友好:Web Console
- 开源免费:Apache 2.0 协议
- 持续更新:社区活跃

适用场景:
- 线上问题排查
- 动态追踪方法调用
- 查看类加载信息
- 监控方法执行时间
- 反编译代码
- 修改日志级别

3.2 安装和启动

方式 1:使用 arthas-boot(推荐)

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

## 启动 Arthas
java -jar arthas-boot.jar

## 输出示例:
## [INFO] arthas-boot version: 3.7.1
## [INFO] Found existing java process, please choose one and input the pid.
## [1] 12345 com.example.Application
## [2] 12346 org.gradle.launcher.daemon.bootstrap.GradleDaemon
## [3] 12347 sun.tools.jps.Jps
## Input the pid: 1

## 选择要诊断的进程编号,回车连接

## 直接指定 PID
java -jar arthas-boot.jar 12345

方式 2:下载完整包

bash
## 下载
wget https://arthas.aliyun.com/download/arthas-bin.zip

## 解压
unzip arthas-bin.zip -d arthas

## 启动
java -jar arthas/arthas-boot.jar

方式 3:使用 Docker

bash
## 拉取镜像
docker pull hengyunabc/arthas:latest

## 启动容器
docker run -it --rm \
  --pid=container:<container_id> \
  hengyunabc/arthas:latest \
  java -jar /opt/arthas/arthas-boot.jar

退出 Arthas:

bash
## 退出当前会话(不停止 Arthas)
quit

## 完全退出 Arthas(停止 Arthas 服务)
stop

3.3 常用命令

1. dashboard:仪表盘
bash
## 查看综合仪表盘
dashboard

## 输出示例:
## Threads                                                                   Memory                     Runtime
## ID   NAME                           GROUP          PRIORITY   STATE     %CPU used      total     max      usage   version  11.0.11
## 1    main                           main           5          TIMED_WAI 0.0  128M      256M      512M     25.00%  pid      12345
## 2    Reference Handler              system         10         WAITING   0.0  
## 3    Finalizer                      system         8          WAITING   0.0   GARBAGE-COLLECTORS
## 4    Signal Dispatcher              system         9          RUNNABLE  0.0   PS Scavenge        [count : 15, time : 156ms]
## 5    Attach Listener                system         5          RUNNABLE  0.0   PS MarkSweep       [count : 2, time : 234ms]

## 按下 q 或 Ctrl+C 退出 dashboard

仪表盘信息解读:

code
Threads 区域:
- ID:线程 ID
- NAME:线程名称
- GROUP:线程组
- PRIORITY:优先级
- STATE:线程状态
- %CPU:CPU 使用率

Memory 区域:
- used:已用内存
- total:总内存
- max:最大内存
- usage:使用率

Runtime 区域:
- version:JVM 版本
- pid:进程 ID

GARBAGE-COLLECTORS 区域:
- count:GC 次数
- time:GC 时间
2. thread:线程命令
bash
## 查看所有线程
thread

## 输出示例:
## Threads Total: 50, New: 0, Runnable: 10, Blocked: 5, Waiting: 20, TimedWaiting: 15
## 
## ID   NAME                           GROUP          PRIORITY   STATE     %CPU  TIME     INTERRUPTED DAEMON
## 1    main                           main           5          TIMED_WAI 0.0   0:0.123  false       false
## 2    Reference Handler              system         10         WAITING   0.0   0:0.001  false       true
## 3    Finalizer                      system         8          WAITING   0.0   0:0.002  false       true

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

## 输出示例:
## "http-nio-8080-exec-10" Id=123 cpuUsage=95.0% deltaTime=0.1s time=0:0:10
##     at com.example.service.CalculateService.calculate(CalculateService.java:25)
##     at com.example.controller.UserController.getUser(UserController.java:50)
##     ...

## 查看特定线程
thread <thread_id>

## 查看阻塞状态的线程
thread -state BLOCKED

## 输出示例:
## "thread-1" Id=15 BLOCKED
##     at com.example.Demo.method(Demo.java:25)
##     - waiting to lock <0x00000000c0001234> (a java.lang.Object)
##     - locked <0x00000000c0005678> (a java.lang.Object)

## 查看死锁
thread -b

## 输出示例:
## "thread-1" Id=15 BLOCKED
##     at com.example.DeadlockDemo.method1(DeadlockDemo.java:20)
##     - waiting to lock <0x00000000c0005678> (a java.lang.Object)
##     - locked <0x00000000c0001234> (a java.lang.Object)
## 
## "thread-2" Id=16 BLOCKED
##     at com.example.DeadlockDemo.method2(DeadlockDemo.java:30)
##     - waiting to lock <0x00000000c0001234> (a java.lang.Object)
##     - locked <0x00000000c0005678> (a java.lang.Object)
## 
## Found 1 deadlock.
3. jvm:JVM 信息
bash
## 查看 JVM 信息
jvm

## 输出示例:
## MEMORY
## MEMORY-MAX                 512M
## MEMORY-TOTAL               256M
## MEMORY-USED                128M

## THREAD
## THREAD-COUNT               50
## DAEMON-COUNT               45
## PEAK-COUNT                 60
## STARTED-COUNT              100
## DEADLOCK-COUNT             0

## GARBAGE-COLLECTORS
## PS Scavenge                [count : 15, time : 156ms]
## PS MarkSweep               [count : 2, time : 234ms]

## CLASS-LOADING
## LOADED-CLASS-COUNT         5000
## TOTAL-LOADED-CLASS-COUNT   5200
## UNLOADED-CLASS-COUNT       200
4. watch:方法执行观察

作用: 观察方法执行,包括参数、返回值、异常、执行时间等。

bash
## 基本语法
watch <类名> <方法名> <观察表达式> [条件表达式] [选项]

## 观察方法执行(查看参数和返回值)
watch com.example.service.UserService getUser "{params, returnObj}"

## 输出示例:
## method=com.example.service.UserService.getUser location=AtExit
## ts=2026-03-30 00:00:00; result=@ArrayList[
##     @Object[][
##         @Integer[123],
##     ],
##     @User[
##         id=@Integer[123],
##         name=@String[张三],
##         age=@Integer[25],
##     ],
## ]

## 观察方法执行时间
watch com.example.service.UserService getUser "{params, returnObj, #cost}" -x 2

## 输出示例:
## method=com.example.service.UserService.getUser location=AtExit
## ts=2026-03-30 00:00:00; result=@ArrayList[
##     @Object[][
##         @Integer[123],
##     ],
##     @User[
##         id=@Integer[123],
##         name=@String[张三],
##     ],
##     @Double[15.23],  # 执行时间:15.23ms
## ]

## 观察方法异常
watch com.example.service.UserService getUser "{params, throwExp}" -e -x 2

## 输出示例:
## method=com.example.service.UserService.getUser location=AtExceptionExit
## ts=2026-03-30 00:00:00; result=@ArrayList[
##     @Object[][
##         @Integer[123],
##     ],
##     @UserNotFoundException[
##         message=@String[用户不存在],
##     ],
## ]

## 条件过滤(只观察特定参数)
watch com.example.service.UserService getUser "{params, returnObj}" "params[0]==123"

## 观察方法调用前(-b)
watch com.example.service.UserService getUser "{params, target}" -b -x 2

## 观察方法返回后(-s,默认)
watch com.example.service.UserService getUser "{params, returnObj}" -s -x 2

## 观察方法结束后(-f,正常返回或异常)
watch com.example.service.UserService getUser "{params, returnObj, throwExp}" -f -x 2

watch 参数详解:

code
观察点:
-b : 方法调用前
-e : 方法异常后
-s : 方法返回后(默认)
-f : 方法结束后(正常返回或异常)

观察对象:
params     : 方法参数数组
returnObj  : 返回值
throwExp   : 异常
target     : 当前对象
this       : 当前对象(同 target)
class      : 当前类
method     : 当前方法
#cost      : 执行时间(ms)
#process   : 当前处理时间(调用前为负数)

输出深度:
-x 1 : 输出第一层属性(默认)
-x 2 : 输出第二层属性
-x 3 : 输出第三层属性
-x 4 : 输出第四层属性

条件表达式:
"params[0]==123"           : 第一个参数等于 123
"params[0].id>100"         : 第一个参数的 id 属性大于 100
"#cost>100"                : 执行时间大于 100ms
"throwExp!=null"           : 有异常
5. trace:方法调用追踪

作用: 追踪方法调用路径,显示每个方法的执行时间。

bash
## 基本语法
trace <类名> <方法名> [条件表达式] [选项]

## 追踪方法调用路径
trace com.example.service.UserService getUser

## 输出示例:
## `---[15.23ms] com.example.service.UserService:getUser()
##     +---[10.12ms] com.example.dao.UserDao:selectById() #23
##     +---[3.45ms] com.example.cache.CacheManager:get() #24
##     `---[1.66ms] com.example.util.ObjectUtil:convert() #25

## 解读:
## - getUser() 总耗时 15.23ms
## - selectById() 耗时 10.12ms,代码行号 #23
## - CacheManager.get() 耗时 3.45ms,代码行号 #24
## - ObjectUtil.convert() 耗时 1.66ms,代码行号 #25

## 追踪方法调用路径(包含 JDK 方法)
trace com.example.service.UserService getUser --skipJDKMethod false

## 条件过滤
trace com.example.service.UserService getUser "params[0]==123"

## 过滤执行时间大于 100ms 的调用
trace com.example.service.UserService getUser "#cost>100"

## 追踪多次调用
trace com.example.service.UserService getUser -n 5

## 保存追踪结果到文件
trace com.example.service.UserService getUser >> trace_output.txt
6. stack:方法调用堆栈

作用: 查看方法被调用的完整堆栈。

bash
## 查看方法调用堆栈
stack com.example.service.UserService getUser

## 输出示例:
## threadName=http-nio-8080-exec-1
## id=10
## methodName=getUser
## object=com.example.service.UserService@12345678
## 
##   at com.example.service.UserService.getUser(UserService.java:25)
##   at com.example.controller.UserController.getUser(UserController.java:50)
##   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
##   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
##   at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
##   at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)

## 条件过滤
stack com.example.service.UserService getUser "params[0]==123"
7. jad:反编译

作用: 反编译类或方法,查看运行时代码。

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

## 输出示例:
## ClassInfo:
##   class-name: com.example.service.UserService
##   code-source: /app/classes/
##   classLoaderHash: 12345678
## 
## public class UserService {
##     public User getUser(Integer id) {
##         User user = userDao.selectById(id);
##         return user;
##     }
## }

## 反编译特定方法
jad com.example.service.UserService getUser

## 反编译并显示行号
jad --lineNumber true com.example.service.UserService

## 反编译内部类
jad com.example.service.UserService\$InnerClass
8. sc:查看类信息
bash
## 查看类信息
sc com.example.service.UserService

## 输出示例:
## class-info        com.example.service.UserService
## code-source       /app/classes/
## name              com.example.service.UserService
## isInterface       false
## isAnnotation      false
## isEnum            false
## isAnonymousClass  false
## isArray           false
## isLocalClass      false
## isMemberClass     false
## isPrimitive       false
## isSynthetic       false
## simple-name       UserService
## modifier          public
## annotation        
## interfaces        
## super-class       +-java.lang.Object
## class-loader      +-sun.misc.Launcher$AppClassLoader@18b4aac2
##                     +-sun.misc.Launcher$ExtClassLoader@6108b2d7
## ClassLoader       sun.misc.Launcher$AppClassLoader@18b4aac2

## 查看类字段
sc -d com.example.service.UserService

## 模糊搜索类
sc com.example.service.*
9. sm:查看方法信息
bash
## 查看方法信息
sm com.example.service.UserService

## 输出示例:
## com.example.service.UserService->getUser
## com.example.service.UserService->saveUser
## com.example.service.UserService->deleteUser

## 查看方法详细信息
sm -d com.example.service.UserService getUser

## 输出示例:
## declaring-class  com.example.service.UserService
## method-name      getUser
## modifier         public
## annotation       
## parameters       java.lang.Integer
## return           com.example.model.User
## exceptions       
## classLoaderHash  12345678

## 模糊搜索方法
sm com.example.service.UserService get*
10. monitor:方法调用统计
bash
## 监控方法调用统计
monitor -c 5 com.example.service.UserService getUser

## 输出示例:
## timestamp      class              method     total  success  fail  avg-rt(ms)  fail-rate
## --------------------------------------------------------------------------------------------
## 2026-03-30 00  UserService        getUser    100    95       5     15.23       5.00%
## 2026-03-30 01  UserService        getUser    120    118      2     12.45       1.67%
## 2026-03-30 02  UserService        getUser    150    150      0     10.12       0.00%

## 参数说明:
## -c 5:每 5 秒输出一次统计
## total:调用次数
## success:成功次数
## fail:失败次数
## avg-rt:平均响应时间
## fail-rate:失败率
11. profiler:性能分析
bash
## 启动 CPU 性能分析
profiler start

## 输出示例:
## Started [cpu] profiling

## 执行业务操作...

## 停止性能分析
profiler stop

## 输出示例:
## CPU Profiling result:
##   25.12%  com.example.service.CalculateService.calculate()
##   15.34%  com.example.service.UserService.getUser()
##   10.23%  com.example.cache.CacheManager.get()

## 启动内存分配分析
profiler start --event alloc

## 停止内存分配分析
profiler stop

## 输出示例:
## Memory Allocation Profiling result:
##   40.12%  byte[]
##   25.34%  java.lang.String
##   15.23%  java.util.HashMap$Node

## 查看 profiler 状态
profiler status

## 输出示例:
## Profiling is running for 60 seconds

3.4 Arthas 实战案例

案例 1:排查 CPU 高

场景: 线上 CPU 使用率 95%,需要快速定位。

bash
## 1. 启动 Arthas
java -jar arthas-boot.jar

## 2. 查看仪表盘,确认 CPU 高
dashboard

## 输出示例:
## ID   NAME                           GROUP   PRIORITY   STATE     %CPU
## 123  http-nio-8080-exec-10          main    5          RUNNABLE  95.0

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

## 输出示例:
## "http-nio-8080-exec-10" Id=123 cpuUsage=95.0% deltaTime=0.1s time=0:0:10
##     at com.example.service.CalculateService.calculate(CalculateService.java:25)
##     at com.example.controller.UserController.getUser(UserController.java:50)

## 4. 反编译查看代码
jad com.example.service.CalculateService calculate

## 输出示例:
## public int calculate(int n) {
##     while (true) {  // 死循环!
##         n++;
##         if (n < 0) break;
##     }
##     return n;
## }

## 5. 观察方法执行
watch com.example.service.CalculateService calculate "{params, returnObj, #cost}" -x 2

## 输出示例:
## method=calculate location=AtExit
## ts=2026-03-30 00:00:00; result=@ArrayList[
##     @Object[][@Integer[1]],
##     @Integer[Integer.MIN_VALUE],
##     @Double[5000.12],  # 执行时间 5 秒!
## ]

解决方案: 修复死循环代码。

案例 2:排查方法慢

场景: 某个接口响应慢,需要定位瓶颈。

bash
## 1. 启动 Arthas
java -jar arthas-boot.jar

## 2. 追踪方法调用
trace com.example.service.UserService getUser

## 输出示例:
## `---[123.45ms] com.example.service.UserService:getUser()
##     +---[100.23ms] com.example.dao.UserDao:selectById() #23
##     +---[20.12ms] com.example.cache.CacheManager:get() #24

## 3. 继续追踪慢的方法
trace com.example.dao.UserDao selectById

## 输出示例:
## `---[100.23ms] com.example.dao.UserDao:selectById()
##     +---[80.12ms] java.sql.PreparedStatement:executeQuery() #15
##     +---[15.23ms] java.sql.ResultSet:next() #18

## 4. 发现数据库查询慢,检查 SQL 或索引

解决方案: 优化 SQL 或添加索引。

案例 3:动态修改日志级别

场景: 生产环境需要临时开启 DEBUG 日志排查问题。

bash
## 1. 启动 Arthas
java -jar arthas-boot.jar

## 2. 查看当前日志级别
ognl '@org.slf4j.LoggerFactory@getLogger("com.example").getLevel()'

## 输出示例:
## @Level[INFO]

## 3. 修改日志级别为 DEBUG
ognl '@org.slf4j.LoggerFactory@getLogger("com.example").setLevel(@ch.qos.logback.classic.Level@DEBUG)'

## 4. 验证日志级别
ognl '@org.slf4j.LoggerFactory@getLogger("com.example").getLevel()'

## 输出示例:
## @Level[DEBUG]

## 5. 排查问题后恢复日志级别
ognl '@org.slf4j.LoggerFactory@getLogger("com.example").setLevel(@ch.qos.logback.classic.Level@INFO)'
案例 4:排查内存泄漏

场景: 应用内存持续增长,疑似内存泄漏。

bash
## 1. 启动 Arthas
java -jar arthas-boot.jar

## 2. 查看 JVM 内存信息
jvm

## 输出示例:
## MEMORY
## MEMORY-MAX                 512M
## MEMORY-TOTAL               512M
## MEMORY-USED                480M  # 使用率 93%!

## 3. 查看类统计
sc -d com.example.*

## 输出示例:
## class-info        com.example.model.UserSession
## isInterface       false
## ...

## 4. 查看对象数量
vmtool --action getInstances --className com.example.model.UserSession --limit 10

## 输出示例:
## @UserSession[][
##     @UserSession[id=1, name=user1],
##     @UserSession[id=2, name=user2],
##     ...
## ]

## 5. 生成堆转储
heapdump /tmp/heap.hprof

## 输出示例:
## heapdump file created: /tmp/heap.hprof

## 6. 使用 MAT 分析 heap.hprof

四、GC 日志分析

4.1 GC 日志配置

JDK 8 GC 日志配置:

bash
## 开启 GC 日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:/logs/gc.log

## 完整配置
java \
  -XX:+PrintGCDetails \
  -XX:+PrintGCDateStamps \
  -XX:+PrintGCTimeStamps \
  -XX:+PrintGCApplicationStoppedTime \
  -XX:+PrintGCApplicationConcurrentTime \
  -XX:+PrintTenuringDistribution \
  -Xloggc:/logs/gc.log \
  -XX:+UseGCLogFileRotation \
  -XX:NumberOfGCLogFiles=5 \
  -XX:GCLogFileSize=10M \
  -jar app.jar

JDK 11+ GC 日志配置:

bash
## 统一使用 -Xlog 参数
-Xlog:gc*:file=/logs/gc.log:time,level,tags:filecount=5,filesize=10m

## 完整配置
java \
  -Xlog:gc*,gc+heap=debug,gc+age=trace:file=/logs/gc.log:time,level,tags:filecount=5,filesize=10m \
  -jar app.jar

4.2 GC 日志解读

Young GC 日志示例(JDK 8):

code
2026-03-30T00:00:00.123+0800: 15.234: [GC (Allocation Failure) [PSYoungGen: 262144K->32768K(305664K)] 262144K->32832K(1005568K), 0.0123456 secs] [Times: user=0.05 sys=0.01, real=0.01 secs]

解读:
- 2026-03-30T00:00:00.123+0800: GC 发生时间
- 15.234: JVM 启动后的秒数
- [GC (Allocation Failure)]: GC 原因(分配失败)
- [PSYoungGen: ...]: Young GC
- 262144K->32768K: 新生代使用从 262MB 降到 32MB
- (305664K): 新生代总容量
- 262144K->32832K: 堆使用从 262MB 降到 32MB
- (1005568K): 堆总容量
- 0.0123456 secs: GC 耗时
- [Times: user=0.05 sys=0.01, real=0.01 secs]: 用户态、内核态、实际时间

Full GC 日志示例(JDK 8):

code
2026-03-30T00:00:00.456+0800: 45.678: [Full GC (Ergonomics) [PSYoungGen: 32768K->0K(305664K)] [ParOldGen: 699392K->699392K(700416K)] 732160K->699392K(1006080K), [Metaspace: 51200K->51200K(1097728K)], 0.1234567 secs] [Times: user=1.23 sys=0.12, real=0.12 secs]

解读:
- [Full GC (Ergonomics)]: Full GC,原因是 Ergonomics(自动调优)
- [PSYoungGen: 32768K->0K]: 新生代从 32MB 降到 0
- [ParOldGen: 699392K->699392K]: 老年代没有变化(可能已满)
- 732160K->699392K: 堆使用从 732MB 降到 699MB
- [Metaspace: 51200K->51200K]: Metaspace 没有变化
- 0.1234567 secs: GC 耗时 123ms

G1 GC 日志示例(JDK 11+):

code
[0.123s][info][gc] GC(0) Pause Young (Allocation Failure) 25M->5M(100M) 10.123ms
[0.456s][info][gc] GC(1) Pause Young (Allocation Failure) 30M->10M(100M) 12.456ms
[1.234s][info][gc] GC(2) Pause Mixed (G1 Evacuation Pause) 80M->30M(100M) 45.678ms
[2.345s][info][gc] GC(3) Pause Full (Ergonomics) 95M->50M(100M) 123.456ms

解读:
- GC(0): GC 编号
- Pause Young: Young GC
- Pause Mixed: Mixed GC(G1 特有)
- Pause Full: Full GC
- 25M->5M: 回收前后堆使用
- (100M): 堆总大小
- 10.123ms: GC 耗时

4.3 GC 日志分析工具

1. GCViewer(开源)

bash
## 下载
wget https://github.com/chewiebug/GCViewer/releases/download/1.36/gcviewer-1.36.jar

## 使用
java -jar gcviewer-1.36.jar gc.log

## 功能:
## - 可视化 GC 日志
## - 统计 GC 次数、时间
## - 内存使用趋势图

2. GCEasy(在线)

code
网址: https://gceasy.io/

使用方法:
1. 上传 GC 日志文件
2. 自动分析
3. 查看报告

报告内容:
- GC 统计(次数、时间)
- 内存使用趋势
- GC 停顿时间
- 内存泄漏检测
- 优化建议

3. GCPlot(开源)

code
网址: https://github.com/gcplot/gcplot

功能:
- 批量分析 GC 日志
- 对比多次 GC 日志
- 生成详细报告

4.4 GC 日志分析实战

案例 1:频繁 Full GC

code
GC 日志:
[10.123s][info][gc] GC(15) Pause Full (Ergonomics) 512M->500M(512M) 1234.567ms
[12.456s][info][gc] GC(16) Pause Full (Ergonomics) 512M->505M(512M) 1456.789ms
[14.789s][info][gc] GC(17) Pause Full (Ergonomics) 512M->510M(512M) 1678.901ms

分析:
- 频繁 Full GC(每 2-3 秒一次)
- GC 后内存使用不下降(500M->505M->510M)
- GC 时间越来越长(1234ms->1456ms->1678ms)

原因:
- 内存泄漏
- 堆内存不足
- 大对象过多

解决方案:
1. 增大堆内存(-Xmx1g)
2. 生成 heap dump 分析内存泄漏
3. 使用 G1 GC 减少停顿时间

案例 2:Young GC 频繁

code
GC 日志:
[0.123s][info][gc] GC(0) Pause Young (Allocation Failure) 10M->2M(20M) 1.234ms
[0.234s][info][gc] GC(1) Pause Young (Allocation Failure) 10M->3M(20M) 1.567ms
[0.345s][info][gc] GC(2) Pause Young (Allocation Failure) 11M->4M(20M) 1.890ms

分析:
- Young GC 非常频繁(每 0.1 秒一次)
- 新生代只有 20MB,太小
- 对象快速填满 Eden 区

原因:
- 新生代太小
- 对象创建速度过快

解决方案:
1. 增大新生代(-Xmn256m)
2. 调整 NewRatio(-XX:NewRatio=1)
3. 优化代码减少对象创建

五、线程 Dump 分析

5.1 生成线程 Dump

方式 1:使用 jstack

bash
## 生成线程 Dump
jstack <pid> > thread_dump.txt

## 生成详细线程 Dump(包含锁信息)
jstack -l <pid> > thread_dump_detail.txt

## 强制生成(当进程无响应时)
jstack -F <pid> > thread_dump_force.txt

方式 2:使用 jcmd

bash
## 生成线程 Dump
jcmd <pid> Thread.print > thread_dump.txt

## 生成详细线程 Dump
jcmd <pid> Thread.print -l > thread_dump_detail.txt

方式 3:使用 Arthas

bash
## 启动 Arthas
java -jar arthas-boot.jar

## 生成线程 Dump
thread > thread_dump.txt

## 查看线程栈
thread <thread_id>

方式 4:发送信号( Linux/Unix)

bash
## 发送 SIGQUIT 信号
kill -3 <pid>

## 线程 Dump 会输出到标准输出或日志文件
## Tomcat:输出到 catalina.out
## Spring Boot:输出到控制台或日志文件

方式 5:使用 VisualVM

code
1. 启动 VisualVM
2. 连接到 JVM 进程
3. 切换到 "线程" 标签
4. 点击 "Thread Dump" 按钮
5. 查看线程 Dump

5.2 线程 Dump 分析技巧

技巧 1:统计线程状态

bash
## 统计各种状态的线程数量
jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c | sort -rn

## 输出示例:
##      50    java.lang.Thread.State: TIMED_WAITING (sleeping)
##      30    java.lang.Thread.State: WAITING (on object monitor)
##      20    java.lang.Thread.State: RUNNABLE
##      10    java.lang.Thread.State: BLOCKED (on object monitor)
##       5    java.lang.Thread.State: WAITING (parking)

分析:
- RUNNABLE:正常状态,正在运行
- BLOCKED:锁竞争严重,需要优化
- WAITING:等待唤醒,可能是正常的
- TIMED_WAITING:睡眠或定时等待

技巧 2:查找 BLOCKED 线程

bash
## 查找所有 BLOCKED 线程
jstack <pid> | grep -B 2 "BLOCKED"

## 输出示例:
## "thread-1" Id=15 BLOCKED
##     at com.example.Demo.method(Demo.java:25)

## 查找等待锁的线程
jstack <pid> | grep -A 5 "waiting to lock"

## 输出示例:
##         at com.example.Demo.method(Demo.java:25)
##         - waiting to lock <0x00000000c0001234> (a java.lang.Object)
##         - locked <0x00000000c0005678> (a java.lang.Object)

## 查找持有锁的线程
jstack <pid> | grep -A 5 "locked <0x00000000c0001234>"

技巧 3:检测死锁

bash
## jstack 自动检测死锁
jstack <pid> | grep -A 30 "Found one Java-level deadlock"

## 或使用 Arthas
thread -b

技巧 4:分析线程栈热点

bash
## 提取所有线程栈中的方法调用
jstack <pid> | grep "at " | sort | uniq -c | sort -rn | head -20

## 输出示例:
##     50    at java.lang.Object.wait(Native Method)
##     30    at java.net.SocketInputStream.socketRead0(Native Method)
##     20    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
##     10    at com.example.service.UserService.getUser(UserService.java:25)

分析:
- wait/notify:正常的等待/通知机制
- socketRead0:网络 I/O 等待
- park:Lock 锁等待
- 业务方法:需要重点关注

5.3 线程 Dump 分析工具

1. TDA(Thread Dump Analyzer)

code
下载: https://github.com/iroise/tda

功能:
- 可视化线程 Dump
- 自动检测死锁
- 统计线程状态
- 查找阻塞线程

2. FastThread(在线)

code
网址: https://fastthread.io/

使用方法:
1. 上传线程 Dump 文件
2. 自动分析
3. 查看报告

报告内容:
- 线程统计
- 死锁检测
- 阻塞线程
- CPU 高线程
- 优化建议

3. Spotify Java Thread Dump Analyzer(在线)

code
网址: https://spotify.github.io/threaddump-analyzer/

功能:
- 在线分析
- 可视化展示
- 统计线程状态

六、堆内存分析

6.1 生成堆转储

方式 1:使用 jmap

bash
## 生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>

## 只包含存活对象(会触发 Full GC)
jmap -dump:live,format=b,file=heap_live.hprof <pid>

方式 2:使用 jcmd

bash
## 生成堆转储
jcmd <pid> GC.heap_dump /tmp/heap.hprof

方式 3:使用 Arthas

bash
## 启动 Arthas
java -jar arthas-boot.jar

## 生成堆转储
heapdump /tmp/heap.hprof

## 只包含存活对象
heapdump --live /tmp/heap_live.hprof

方式 4:OOM 时自动生成

bash
## 启动时配置
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/heapdump.hprof -jar app.jar

## OOM 时自动生成堆转储到指定路径

方式 5:使用 VisualVM

code
1. 启动 VisualVM
2. 连接到 JVM 进程
3. 右键进程 → Heap Dump
4. 或在监控面板点击 "Heap Dump" 按钮

6.2 MAT 分析技巧

MAT(Memory Analyzer Tool)是最强大的堆转储分析工具。

安装 MAT
code
下载: https://eclipse.dev/mat/

安装:
1. 下载对应系统的版本
2. 解压
3. 运行 MemoryAnalyzer

注意:
- MAT 需要 JDK 8+ 环境
- 打开大堆转储文件需要调整 MAT 内存
- 编辑 MemoryAnalyzer.ini,添加 -Xmx4g
MAT 分析功能

1. Leak Suspects Report(泄漏检测报告)

code
功能:
- 自动检测内存泄漏
- 显示疑似泄漏对象

使用:
1. 打开 heap dump 文件
2. 选择 "Leak Suspects Report"
3. 查看报告

报告内容:
- Problem Suspect 1:疑似泄漏点
- Problem Suspect 2:疑似泄漏点
- ...

2. Dominator Tree(支配树)

code
功能:
- 查看对象支配树
- 找到占用内存最大的对象

使用:
1. 切换到 "Dominator Tree" 标签
2. 按大小排序
3. 找到占用内存最大的对象
4. 右键 → Path to GC Roots → exclude all phantom/weak/soft etc.
5. 查看引用链

技巧:
- 浅堆(Shallow Heap):对象本身占用内存
- 深堆(Retained Heap):对象及其引用对象占用内存
- 关注深堆大的对象

3. Histogram(直方图)

code
功能:
- 按类统计对象数量和大小

使用:
1. 切换到 "Histogram" 标签
2. 按大小或数量排序
3. 找到对象数量异常或大小异常的类

技巧:
- 关注对象数量异常的类
- 关注占用内存最大的类
- 对比多个 heap dump,找出增长的对象

4. OQL(Object Query Language)

code
功能:
- 使用 SQL 语法查询对象

语法:
SELECT <选择列表>
FROM <类名>
WHERE <条件>

示例:
## 查询长度大于 1000 的字符串
SELECT s FROM java.lang.String s WHERE s.count > 1000

## 查询所有 UserSession 对象
SELECT s FROM com.example.model.UserSession s

## 查询特定 ID 的用户
SELECT u FROM com.example.model.User u WHERE u.id = 123

## 统计对象数量
SELECT COUNT(s) FROM java.lang.String s
MAT 实战案例

案例 1:查找内存泄漏

bash
## 1. 打开 heap dump 文件
## 2. 选择 "Leak Suspects Report"

## 输出示例:
## Problem Suspect 1:
## The class java.util.HashMap$Node[], loaded by <system class loader>, 
## occupies 500.00MB (50.00%) bytes. The instance of java.util.HashMap$Node[] 
## is referenced by com.example.cache.SessionCache.sessions.

## 3. 点击 "Details" 查看详情

## 4. 切换到 "Dominator Tree"
## 5. 找到 SessionCache.sessions
## 6. 右键 → Path to GC Roots → exclude all phantom/weak/soft etc.

## 输出示例:
## GC Root: static field SessionCache.sessions
##   └── HashMap$Node[]
##       └── HashMap$Node
##           └── UserSession (50000 instances)

## 7. 定位到 SessionCache.sessions 字段
## 8. 发现缓存未清理,导致内存泄漏

案例 2:查找大对象

bash
## 1. 切换到 "Dominator Tree"
## 2. 按 Retained Heap 排序

## 输出示例:
## Class                | Shallow Heap | Retained Heap
## -------------------- | ------------ | -------------
## byte[10000000]       | 10,000,016   | 10,000,016
## UserSession[50000]   | 200,000      | 500,000,000
## ArrayList            | 24           | 50,000,024

## 3. 发现 UserSession 数组占用 500MB
## 4. 查看 UserSession 对象详情

## 5. 切换到 "Histogram"
## 6. 按 Retained Heap 排序

## 输出示例:
## Class                | Objects   | Shallow Heap | Retained Heap
## -------------------- | --------- | ------------ | -------------
## UserSession          | 50,000    | 2,000,000    | 500,000,000
## byte[]               | 10,000    | 100,000,000  | 100,000,000
## String               | 100,000   | 2,400,000    | 50,000,000

## 7. 发现 UserSession 对象数量异常(50000 个)
## 8. 查看引用链,找到持有这些对象的地方

6.3 在线堆分析工具

1. Heaphero(在线)

code
网址: https://heaphero.io/

使用方法:
1. 上传 heap dump 文件
2. 自动分析
3. 查看报告

报告内容:
- 内存使用统计
- 对象统计
- 内存泄漏检测
- 优化建议

2. GCEasy(在线)

code
网址: https://gceasy.io/

功能:
- GC 日志分析
- 堆 dump 分析

七、线上问题排查完整流程

7.1 问题分类与排查入口

code
┌────────────────────────────────────────────────────────────────────────────┐
│                        线上问题排查入口                                    │
├─────────────┬──────────────────────┬──────────────────────────────────────┤
│  问题类型   │  现象                │  排查入口                             │
├─────────────┼──────────────────────┼──────────────────────────────────────┤
│  CPU 飙高   │ CPU 使用率持续高     │ top、jstack、thread -n、Arthas        │
├─────────────┼──────────────────────┼──────────────────────────────────────┤
│  内存问题   │ 内存持续增长、OOM    │ jstat、jmap、heapdump、MAT            │
├─────────────┼──────────────────────┼──────────────────────────────────────┤
│  GC 问题    │ 频繁 GC、停顿长      │ jstat、GC 日志、jcmd                  │
├─────────────┼──────────────────────┼──────────────────────────────────────┤
│  线程问题   │ 死锁、阻塞、超时     │ jstack、thread -b、thread -state      │
├─────────────┼──────────────────────┼──────────────────────────────────────┤
│  类加载问题 │ ClassNotFoundException │ jcmd、classloader、sc              │
├─────────────┼──────────────────────┼──────────────────────────────────────┤
│  性能问题   │ 响应慢、吞吐低       │ trace、watch、profiler               │
└─────────────┴──────────────────────┴──────────────────────────────────────┘

7.2 CPU 飙高排查流程

完整排查步骤
bash
## 步骤 1:确认 CPU 高的进程
top

## 输出示例:
##   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
##  12345 appuser   20   0  4.0g   1.2g   120m S 95.0  7.5   0:10.23 java

## 步骤 2:查看高 CPU 的线程
top -Hp 12345

## 输出示例:
##   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
##  12346 appuser   20   0  4.0g   1.2g   120m R 90.0  7.5   0:09.15 java
##  12347 appuser   20   0  4.0g   1.2g   120m S  2.0  7.5   0:00.10 java

## 步骤 3:转换线程 ID 为十六进制
printf "%x\n" 12346
## 输出: 303a

## 步骤 4:查看线程栈
jstack 12345 | grep -A 20 "nid=0x303a"

## 或使用 Arthas
java -jar arthas-boot.jar 12345
thread -n 3

## 步骤 5:定位到代码
jad com.example.service.CalculateService calculate
常见原因及解决
code
┌──────────────────────────────────────────────────────────────────────┐
│                    CPU 高常见原因                                      │
├──────────────────────────────────────────────────────────────────────┤
│  1. 业务代码问题                                                      │
│     - 死循环                                                         │
│     - 复杂计算                                                       │
│     - 正则表达式回溯                                                  │
│     解决:优化代码、算法优化                                           │
│                                                                      │
│  2. GC 频繁                                                          │
│     - 内存不足                                                       │
│     - 内存泄漏                                                       │
│     解决:增大内存、优化 GC 参数、修复内存泄漏                          │
│                                                                      │
│  3. 线程问题                                                          │
│     - 线程数过多                                                     │
│     - 锁竞争                                                         │
│     解决:优化线程池、减少锁竞争                                       │
└──────────────────────────────────────────────────────────────────────┘

7.3 内存泄漏排查流程

完整排查步骤
bash
## 步骤 1:查看内存使用趋势
jstat -gcutil <pid> 1000 10

## 输出示例:
##   S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
##   0.00  85.00  55.00  75.00  85.00  75.00    150    1.500     2    0.500    2.000
##   0.00  85.00  60.00  78.00  85.00  75.00    152    1.520     2    0.500    2.020
##   0.00  85.00  65.00  80.00  85.00  75.00    154    1.540     2    0.500    2.040

## 老年代持续增长,说明可能有内存泄漏

## 步骤 2:查看堆内存详情
jmap -heap <pid>

## 步骤 3:查看对象统计
jmap -histo <pid> | head -25

## 步骤 4:生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>

## 或使用 jcmd
jcmd <pid> GC.heap_dump /tmp/heap.hprof

## 或使用 Arthas
heapdump /tmp/heap.hprof

## 步骤 5:使用 MAT 分析堆转储
MAT 分析步骤
code
1. 打开 heap dump 文件
2. 选择 "Leak Suspects Report"
3. 查看疑似泄漏点
4. 切换到 "Dominator Tree"
5. 找到占用内存最大的对象
6. 右键 → Path to GC Roots → exclude all phantom/weak/soft etc.
7. 查看引用链
8. 定位到代码

7.4 线程死锁排查流程

完整排查步骤
bash
## 步骤 1:查看线程栈
jstack <pid> | grep -A 10 "Found one Java-level deadlock"

## 或使用 Arthas
thread -b

## 步骤 2:分析死锁线程
jstack <pid> > thread_dump.txt

## 查看死锁详情
grep -A 30 "Found one Java-level deadlock" thread_dump.txt

## 步骤 3:定位死锁代码
## 从线程栈中找到持有锁的代码位置
死锁示例分析
java
// 死锁代码示例
public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            try { Thread.sleep(10); } catch (InterruptedException e) {}
            synchronized (lock2) {  // 等待 lock2,但 lock2 被 Thread 2 持有
                System.out.println("Thread 1: Holding lock 1 & 2...");
            }
        }
    }
    
    public void method2() {
        synchronized (lock2) {
            System.out.println("Thread 2: Holding lock 2...");
            try { Thread.sleep(10); } catch (InterruptedException e) {}
            synchronized (lock1) {  // 等待 lock1,但 lock1 被 Thread 1 持有
                System.out.println("Thread 2: Holding lock 1 & 2...");
            }
        }
    }
}

解决方案:

java
// 方案 1:统一锁顺序
public void transfer(Account from, Account to, BigDecimal amount) {
    Object firstLock = from.getId() < to.getId() ? lockA : lockB;
    Object secondLock = from.getId() < to.getId() ? lockB : lockA;
    
    synchronized (firstLock) {
        synchronized (secondLock) {
            from.debit(amount);
            to.credit(amount);
        }
    }
}

// 方案 2:使用超时锁
if (lockA.tryLock(1, TimeUnit.SECONDS)) {
    try {
        if (lockB.tryLock(1, TimeUnit.SECONDS)) {
            try {
                from.debit(amount);
                to.credit(amount);
            } finally {
                lockB.unlock();
            }
        }
    } finally {
        lockA.unlock();
    }
}

// 方案 3:使用 Lock 的公平锁
private final Lock lock1 = new ReentrantLock(true);
private final Lock lock2 = new ReentrantLock(true);

7.5 频繁 GC 排查流程

完整排查步骤
bash
## 步骤 1:查看 GC 统计
jstat -gcutil <pid> 1000 10

## 步骤 2:查看 GC 日志
tail -f /logs/gc.log

## 或使用 GC 日志分析工具
## 上传到 https://gceasy.io/

## 步骤 3:分析 GC 原因
## - Allocation Failure:内存分配失败,正常
## - System.gc():代码调用 System.gc(),建议移除
## - CMS Concurrent Mark:CMS GC 正常
## - Ergonomics:自动调优,可能是内存不足

## 步骤 4:根据原因优化
## - 内存不足:增大堆内存
## - 新生代太小:增大新生代
## - 对象创建过快:优化代码
## - 内存泄漏:修复内存泄漏

八、生产环境排查案例

8.1 案例 1:CPU 飙高 - 死循环

问题现象:

code
服务 CPU 使用率持续 95%+,响应缓慢。

排查过程:

bash
## 1. 找到高 CPU 进程
top

## 输出:
##   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
##  12345 appuser   20   0  4.0g   1.2g   120m S 95.0  7.5   0:10.23 java

## 2. 找到高 CPU 线程
top -Hp 12345

## 输出:
##   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
##  12346 appuser   20   0  4.0g   1.2g   120m R 90.0  7.5   0:09.15 java

## 3. 转换线程 ID
printf "%x\n" 12346
## 输出: 303a

## 4. 查看线程栈
jstack 12345 | grep -A 20 "nid=0x303a"

## 输出:
## "http-nio-8080-exec-10" Id=123 runnable
##     at com.example.service.CalculateService.calculate(CalculateService.java:25)

## 5. 反编译代码
jad com.example.service.CalculateService calculate

定位问题:

java
public class CalculateService {
    public int calculate(int n) {
        // 死循环:条件永远不满足
        while (true) {
            n++;
            if (n < 0) break;  // int 溢出后才会 < 0
        }
        return n;
    }
}

解决方案:

java
public class CalculateService {
    public int calculate(int n) {
        // 添加循环退出条件
        int maxIterations = 10000;
        int count = 0;
        while (count < maxIterations) {
            n++;
            count++;
        }
        return n;
    }
}

8.2 案例 2:内存泄漏 - 缓存未清理

问题现象:

code
服务运行一段时间后频繁 Full GC,内存持续增长。

排查过程:

bash
## 1. 查看 GC 统计
jstat -gcutil 12345 1000 5

## 输出:
##   S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
##   0.00  85.00  55.00  75.00  85.00  75.00    150    1.500     2    0.500    2.000
##   0.00  85.00  60.00  78.00  85.00  75.00    152    1.520     2    0.500    2.020

## 老年代持续增长,说明可能有内存泄漏

## 2. 生成堆转储
jmap -dump:format=b,file=leak.hprof 12345

## 3. 使用 MAT 分析
## 打开 leak.hprof,查看 Leak Suspects Report

## 输出:
## Problem Suspect 1:
## The class java.util.HashMap$Node[], occupies 500.00MB (50.00%) bytes.
## Referenced by com.example.cache.SessionCache.sessions.

定位问题:

java
public class CacheManager {
    // 缓存只增不减,导致内存泄漏
    private static Map<String, Object> cache = new HashMap<>();
    
    public static void put(String key, Object value) {
        cache.put(key, value);
    }
    
    // 缺少 remove 或清理机制
}

解决方案:

java
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

public class CacheManager {
    // 使用 Guava Cache,设置过期时间和最大容量
    private static Cache<String, Object> cache = CacheBuilder.newBuilder()
        .maximumSize(10000)  // 最大缓存数量
        .expireAfterWrite(10, TimeUnit.MINUTES)  // 写入后 10 分钟过期
        .recordStats()  // 记录统计信息
        .build();
    
    public static void put(String key, Object value) {
        cache.put(key, value);
    }
    
    public static Object get(String key) {
        return cache.getIfPresent(key);
    }
    
    public static void remove(String key) {
        cache.invalidate(key);
    }
    
    public static void clear() {
        cache.invalidateAll();
    }
}

8.3 案例 3:频繁 Young GC

问题现象:

code
Young GC 频繁(每秒 10+ 次),吞吐量下降。

排查过程:

bash
## 1. 查看 GC 统计
jstat -gcutil 12345 1000 5

## 输出:
##   S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
##   0.00  85.00  95.00  45.00  85.00  75.00    150    1.500     0    0.000    1.500
##   0.00  85.00   5.00  45.00  85.00  75.00    151    1.510     0    0.000    1.510  # GC 后
##   0.00  85.00  15.00  45.00  85.00  75.00    151    1.510     0    0.000    1.510  # 快速增长
##   0.00  85.00  25.00  45.00  85.00  75.00    151    1.510     0    0.000    1.510
##   0.00  85.00  35.00  45.00  85.00  75.00    151    1.510     0    0.000    1.510

## Eden 区快速增长,每秒 10+ 次 Young GC

## 2. 查看新生代大小
jinfo -flag NewSize 12345

## 输出:
## -XX:NewSize=10485760  # 新生代只有 10MB,太小!

## 3. 使用 Arthas 观察对象创建
prof start --event alloc

## 执行业务操作...

## 4. 查看分配热点
prof stop

## 输出:
## Memory Allocation Profiling result:
##   40.12%  byte[]
##   25.34%  java.lang.String
##   15.23%  com.example.model.TempData

定位问题:

java
public class DataProcessService {
    public void process(List<String> dataList) {
        // 每次处理创建大量临时对象
        for (String data : dataList) {
            byte[] temp = new byte[1024 * 1024];  // 1MB 临时数组
            // 处理数据
        }
    }
}

解决方案:

java
public class DataProcessService {
    // 方案 1:重用临时数组
    private static final ThreadLocal<byte[]> tempBuffer = 
        ThreadLocal.withInitial(() -> new byte[1024 * 1024]);
    
    public void process(List<String> dataList) {
        byte[] temp = tempBuffer.get();
        for (String data : dataList) {
            // 重用 temp 数组
            // 处理数据
        }
    }
}

// 方案 2:增大新生代
// -Xmn512m  # 新生代从 10MB 增加到 512MB

参数调优:

bash
## 增大新生代
-Xmn512m  # 新生代从 10MB 增加到 512MB

## 或调整新生代比例
-XX:NewRatio=1  # 老年代:新生代 = 1:1(默认是 2:1)

## 或使用 G1 收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

8.4 案例 4:线程死锁

问题现象:

code
应用无响应,请求超时。

排查过程:

bash
## 1. 查看线程栈
jstack 12345

## 输出:
## Found one Java-level deadlock:
## =============================
## "thread-2":
##   waiting to lock monitor 0x00007f8c0c00a800 (object 0x00000000c0001234),
##   which is held by "thread-1"
## "thread-1":
##   waiting to lock monitor 0x00007f8c0c00b800 (object 0x00000000c0005678),
##   which is held by "thread-2"
## 
## Java stack information for the threads listed above:
## ===================================================
## "thread-2":
##         at com.example.DeadlockDemo.method2(DeadlockDemo.java:30)
##         - waiting to lock <0x00000000c0001234> (a java.lang.Object)
##         - locked <0x00000000c0005678> (a java.lang.Object)
## "thread-1":
##         at com.example.DeadlockDemo.method1(DeadlockDemo.java:20)
##         - waiting to lock <0x00000000c0005678> (a java.lang.Object)
##         - locked <0x00000000c0001234> (a java.lang.Object)
## 
## Found 1 deadlock.

定位问题:

java
public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            synchronized (lock2) {
                // 业务逻辑
            }
        }
    }
    
    public void method2() {
        synchronized (lock2) {
            synchronized (lock1) {
                // 业务逻辑
            }
        }
    }
}

解决方案:

java
public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();
    
    // 方案 1:统一锁顺序
    public void method1() {
        synchronized (lock1) {
            synchronized (lock2) {
                // 业务逻辑
            }
        }
    }
    
    public void method2() {
        synchronized (lock1) {  // 改为和 method1 相同的锁顺序
            synchronized (lock2) {
                // 业务逻辑
            }
        }
    }
    
    // 方案 2:使用 ReentrantLock
    private final Lock lock1 = new ReentrantLock();
    private final Lock lock2 = new ReentrantLock();
    
    public void method3() {
        try {
            if (lock1.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    if (lock2.tryLock(1, TimeUnit.SECONDS)) {
                        try {
                            // 业务逻辑
                        } finally {
                            lock2.unlock();
                        }
                    }
                } finally {
                    lock1.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

十、最佳实践

10.1 监控预警

code
┌──────────────────────────────────────────────────────────────────────┐
│                        监控指标建议                                    │
├──────────────────────────────────────────────────────────────────────┤
│  1. CPU 监控                                                         │
│     - CPU 使用率 > 80%:预警                                          │
│     - CPU 使用率 > 90%:告警                                          │
│                                                                      │
│  2. 内存监控                                                         │
│     - 堆内存使用率 > 80%:预警                                        │
│     - 堆内存使用率 > 90%:告警                                        │
│     - 老年代使用率 > 80%:预警                                        │
│     - Metaspace 使用率 > 80%:预警                                   │
│                                                                      │
│  3. GC 监控                                                          │
│     - Young GC 频率 > 10 次/秒:预警                                 │
│     - Full GC 频率 > 1 次/小时:预警                                 │
│     - Full GC 频率 > 1 次/分钟:告警                                 │
│     - GC 停顿时间 > 500ms:预警                                      │
│     - GC 停顿时间 > 1s:告警                                         │
│                                                                      │
│  4. 线程监控                                                         │
│     - 线程数 > 500:预警                                             │
│     - 线程数 > 1000:告警                                            │
│     - 死锁检测:告警                                                 │
│     - BLOCKED 线程 > 10:预警                                        │
│                                                                      │
│  5. 响应时间监控                                                     │
│     - P95 > 1s:预警                                                 │
│     - P95 > 3s:告警                                                 │
│     - 错误率 > 1%:预警                                              │
│     - 错误率 > 5%:告警                                              │
└──────────────────────────────────────────────────────────────────────┘

10.2 JVM 参数配置建议

小型应用(1-2GB 堆):

bash
java \
  -Xms1g \
  -Xmx1g \
  -Xmn512m \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/data/logs/heapdump.hprof \
  -Xlog:gc*:file=/data/logs/gc.log:time,level,tags \
  -jar app.jar

中型应用(4-8GB 堆):

bash
java \
  -Xms4g \
  -Xmx4g \
  -Xmn2g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:InitiatingHeapOccupancyPercent=40 \
  -XX:G1HeapRegionSize=8m \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/data/logs/heapdump.hprof \
  -Xlog:gc*:file=/data/logs/gc.log:time,level,tags:filecount=5,filesize=100m \
  -jar app.jar

大型应用(16GB+ 堆):

bash
java \
  -Xms16g \
  -Xmx16g \
  -XX:+UseZGC \
  -XX:ZCollectionInterval=5 \
  -XX:ConcGCThreads=4 \
  -XX:ParallelGCThreads=8 \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/data/logs/heapdump.hprof \
  -Xlog:gc*:file=/data/logs/gc.log:time,level,tags:filecount=10,filesize=100m \
  -jar app.jar

10.3 排障检查清单

code
┌──────────────────────────────────────────────────────────────────────┐
│                        排障检查清单                                    │
├──────────────────────────────────────────────────────────────────────┤
│  □ 1. 确认问题类型                                                    │
│      □ CPU 高                                                        │
│      □ 内存增长/OOM                                                  │
│      □ GC 频繁                                                       │
│      □ 线程阻塞/死锁                                                 │
│      □ 响应慢                                                        │
│                                                                      │
│  □ 2. 保留现场                                                        │
│      □ 查看监控图表                                                  │
│      □ 保存线程快照                                                  │
│      □ 保存 GC 日志                                                  │
│      □ 必要时生成堆转储                                              │
│      □ 保存系统日志                                                  │
│                                                                      │
│  □ 3. 选择工具                                                        │
│      □ CPU 问题:top、jstack、thread                                 │
│      □ 内存问题:jstat、jmap、MAT                                    │
│      □ GC 问题:jstat、GC 日志                                       │
│      □ 线程问题:jstack、thread                                      │
│                                                                      │
│  □ 4. 分析根因                                                        │
│      □ 定位到具体代码                                                │
│      □ 分析问题原因                                                  │
│      □ 制定解决方案                                                  │
│                                                                      │
│  □ 5. 验证修复                                                        │
│      □ 测试环境验证                                                  │
│      □ 生产环境灰度发布                                              │
│      □ 监控确认问题解决                                              │
│      □ 文档记录问题和解决方案                                        │
└──────────────────────────────────────────────────────────────────────┘

10.4 预防措施

code
1. 代码层面
   - 避免内存泄漏(及时清理缓存、关闭资源)
   - 避免死循环和复杂计算
   - 合理使用线程池
   - 避免嵌套锁

2. 配置层面
   - 合理配置 JVM 参数
   - 开启 GC 日志
   - 开启 HeapDumpOnOutOfMemoryError
   - 配置监控预警

3. 测试层面
   - 压力测试
   - 内存泄漏检测
   - 性能测试
   - 代码审查

4. 监控层面
   - 应用性能监控(APM)
   - JVM 监控
   - 系统监控
   - 日志监控

5. 应急预案
   - 制定排障流程
   - 准备排障脚本
   - 培训团队成员
   - 定期演练

版本差异(旧版 → Java 21)

特性旧版(JDK 8/11)Java 21
命令行工具jps/jstack/jmap/jstat不变;jcmd 合并多数诊断能力
GC 日志-Xloggc(8)-Xlog:gc*(9+,不变)
堆转储jmap -dumpjcmd GC.heap_dump(推荐)
虚拟线程诊断jstack 标识虚拟线程,需区分 carrier
强封装反射可访问内部 APIJDK 17+ 默认强封装,诊断工具需适配
工具版本JDK 8/11 命令差异大JDK 21 建议统一使用 jcmd + 容器感知