文章目录
第一章:工具概览
1.1 三大诊断工具对比
| 工具 | 全名 | 主要功能 | 输出内容 | 使用场景 |
|---|---|---|---|---|
| jstack | Stack Trace for Java | 线程堆栈分析 | 线程状态、调用栈、锁信息、死锁检测 | 死锁检测、CPU飙高定位、线程阻塞分析 |
| jstat | JVM Statistics Monitoring | JVM统计监控 | GC次数、耗时、内存使用率、类加载 | GC监控、内存使用率监控、性能趋势分析 |
| jmap | Memory Map for Java | 内存映射分析 | 堆配置、对象分布、堆转储文件 | 内存泄漏分析、OOM问题排查、大对象查找 |
| jconsole | Java Console | 图形化监控 | 实时监控界面、可视化图表 | 日常监控、问题排查、性能分析 |
1.2 工具选择指南
| 问题类型 | 首选工具 | 辅助工具 | 关键命令 |
|---|---|---|---|
| 死锁 | jstack | jconsole | jstack -l <pid> |
| CPU高 | jstack + top | jconsole | top -H + jstack |
| 频繁GC | jstat | jconsole | jstat -gcutil <pid> 1000 |
| 内存泄漏 | jmap | MAT | jmap -histo + dump |
| OOM | jmap | MAT | jmap -heap + dump |
| 响应慢 | jstack | jstat | jstack(查看BLOCKED) |
| 日常监控 | jconsole | jstat | 图形化界面 |
第二章:jstack - 线程堆栈分析
2.1 核心功能
✅ 死锁检测 - 自动识别并报告死锁
✅ CPU飙高定位 - 配合top找到具体代码行
✅ 线程状态分析 - 查看所有线程的运行状态
✅ 阻塞分析 - 找到被阻塞的线程和原因
2.2 命令语法
jstack [options] <pid>
选项:
-l 打印锁的额外信息
-F 强制dump(当进程挂起时)
-m 打印java和native的堆栈信息
-h 帮助信息
2.3 实用示例
示例1:检测死锁
# 运行死锁程序
java diagnostic.DeadLockDemo
# 使用jstack检测
jstack <pid>
# 输出会自动显示:
Found one Java-level deadlock:
=============================
"Thread-2":
waiting to lock monitor 0x... (object 0x..., a java.lang.Object),
which is held by "Thread-1"
...
Found 1 deadlock.
示例2:定位CPU飙高的线程
# 步骤1:找到Java进程PID
jps -l
# 输出: 12345 diagnostic.HighCPUDemo
# 步骤2:找到占用CPU最高的线程
top -H -p 12345
# 找到TID=12346占用CPU 98%
# 步骤3:转换TID为16进制
printf "%x\n" 12346
# 输出: 303a
# 步骤4:用jstack查看该线程
jstack 12345 | grep -A 30 303a
# 输出:
"High-CPU-Thread" #12 nid=0x303a runnable
at diagnostic.HighCPUDemo.lambda$main$1(HighCPUDemo.java:27)
↑ 定位到具体代码行!
示例3:统计线程状态
# 统计各状态线程数量
jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c
# 输出示例:
50 java.lang.Thread.State: RUNNABLE
200 java.lang.Thread.State: WAITING
10 java.lang.Thread.State: TIMED_WAITING
5 java.lang.Thread.State: BLOCKED ← 如果这个数量多,说明有锁竞争
2.4 线程状态说明
| 状态 | 说明 | 可能原因 | 正常比例 |
|---|---|---|---|
| RUNNABLE | 运行中 | 正常执行 | 10-30% |
| BLOCKED | 阻塞 | 等待获取锁 | <5% |
| WAITING | 等待 | wait()、park() | 50-80% |
| TIMED_WAITING | 限时等待 | sleep()、wait(timeout) | 10-30% |
| TERMINATED | 终止 | 线程结束 | - |
第三章:jstat - JVM统计监控
3.1 核心功能
✅ GC监控 - 实时监控GC次数和耗时
✅ 内存使用率 - 查看各区域使用率
✅ 类加载统计 - 类加载/卸载情况
✅ 性能趋势 - 持续监控,发现趋势
3.2 命令语法
jstat [option] <pid> [interval] [count]
参数:
option 统计选项(-gc, -gcutil等)
pid 进程ID
interval 刷新间隔(毫秒)
count 刷新次数
常用选项:
-gc GC统计
-gcutil GC统计(百分比)
-gccause GC原因
-gcnew 新生代统计
-gcold 老年代统计
-class 类加载统计
-compiler JIT编译统计
3.3 实用示例
示例1:实时监控GC
# 每秒刷新一次,共60次(监控1分钟)
jstat -gcutil <pid> 1000 60
# 输出示例:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 28.50 45.20 35.60 85.30 80.10 120 1.234 5 0.567 1.801
0.00 35.20 52.30 35.80 85.30 80.10 121 1.245 5 0.567 1.812
12.50 0.00 15.40 36.20 85.35 80.10 122 1.253 5 0.567 1.820
↑ 发生了Minor GC,对象从S1复制到S0
# 关键指标:
# - E(Eden)接近100%时会触发Minor GC
# - O(老年代)持续增长可能有内存泄漏
# - YGC增长过快说明对象分配频繁
# - FGC频繁说明老年代压力大
示例2:分析GC原因
jstat -gccause <pid> 1000
# 输出:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT LGCC GCC
0.00 28.57 50.00 7.06 96.54 90.00 15 0.125 2 0.089 0.214 Allocation Failure No GC
↑ 上次GC原因 ↑ 当前GC原因
# 常见GC原因:
# - Allocation Failure: 分配内存失败(正常)
# - System.gc(): 显式调用System.gc()(应避免)
# - Metadata GC Threshold: 元空间达到阈值
# - Ergonomics: 自适应调整
3.4 输出列说明
-gc 输出
| 列 | 说明 | 单位 |
|---|---|---|
| S0C | Survivor0容量 | KB |
| S1C | Survivor1容量 | KB |
| S0U | Survivor0使用量 | KB |
| S1U | Survivor1使用量 | KB |
| EC | Eden容量 | KB |
| EU | Eden使用量 | KB |
| OC | 老年代容量 | KB |
| OU | 老年代使用量 | KB |
| MC | 元空间容量 | KB |
| MU | 元空间使用量 | KB |
| YGC | Young GC次数 | 次 |
| YGCT | Young GC总耗时 | 秒 |
| FGC | Full GC次数 | 次 |
| FGCT | Full GC总耗时 | 秒 |
| GCT | 总GC耗时 | 秒 |
-gcutil 输出
| 列 | 说明 | 范围 |
|---|---|---|
| S0 | Survivor0使用率 | 0-100% |
| S1 | Survivor1使用率 | 0-100% |
| E | Eden使用率 | 0-100% |
| O | 老年代使用率 | 0-100% |
| M | 元空间使用率 | 0-100% |
3.5 性能指标参考
| 指标 | 良好 | 警告 | 严重 |
|---|---|---|---|
| Young GC频率 | <5次/分 | 5-10次/分 | >10次/分 |
| Young GC耗时 | <50ms | 50-100ms | >100ms |
| Full GC频率 | <1次/小时 | 1-5次/小时 | >5次/小时 |
| Full GC耗时 | <500ms | 0.5-1s | >1s |
| GC总耗时占比 | <1% | 1-5% | >5% |
第四章:jmap - 内存映射分析
4.1 核心功能
✅ 堆配置查看 - 查看堆大小、GC配置
✅ 对象统计 - 查看对象数量和占用空间
✅ 内存泄漏分析 - 找出占用内存最多的对象
✅ 堆转储 - 生成heap dump文件供MAT分析
4.2 命令语法
jmap [options] <pid>
选项:
-heap 打印堆配置和使用情况
-histo 打印对象统计
-histo:live 打印存活对象统计(触发GC)
-dump:<dump-options> 生成堆转储文件
-F 强制执行(进程挂起时)
dump选项:
format=b 二进制格式
file=<file> 输出文件
live 只dump存活对象
4.3 实用示例
示例1:查看堆配置
jmap -heap <pid>
# 输出示例:
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 268435456 (256.0MB) ← 最大堆
NewSize = 89128960 (85.0MB) ← 新生代
MaxNewSize = 89128960 (85.0MB)
OldSize = 179306496 (171.0MB) ← 老年代
NewRatio = 2 ← 新生代:老年代 = 1:2
SurvivorRatio = 8 ← Eden:Survivor = 8:1
MetaspaceSize = 21807104 (20.8MB)
MaxMetaspaceSize = unlimited
Heap Usage:
PS Young Generation
Eden Space:
capacity = 67108864 (64.0MB)
used = 32145678 (30.65MB)
free = 34963186 (33.35MB)
47.89% used ← Eden使用率
PS Old Generation
capacity = 179306496 (171.0MB)
used = 125829120 (120.0MB)
free = 53477376 (51.0MB)
70.18% used ← 老年代使用率(如果>80%需要关注)
示例2:查找大对象
# 查看对象分布(前30个)
jmap -histo <pid> | head -30
# 输出示例:
num #instances #bytes class name
----------------------------------------------
1: 50000 120000000 [B ← byte数组,占用114MB
2: 100000 32000000 java.lang.String ← String,占用30MB
3: 12345 12345678 java.util.HashMap$Node
4: 5000 10000000 [C ← char数组
5: 2000 8000000 com.example.LargeObject ← 自定义类
# 查找特定类型
jmap -histo <pid> | grep "\[B" # 所有byte数组
jmap -histo <pid> | grep "com.example" # 自己的包
# 对象类型代码:
# [B = byte[]
# [C = char[]
# [I = int[]
# [L = Object[]
示例3:生成堆转储文件
# 生成完整堆转储
jmap -dump:format=b,file=heap_full.hprof <pid>
# 生成存活对象堆转储(推荐,文件更小)
jmap -dump:live,format=b,file=heap_live.hprof <pid>
# 输出:
Dumping heap to /path/to/heap_live.hprof ...
Heap dump file created [234567890 bytes in 3.5 secs]
# 分析heap dump文件:
# 1. 下载 Eclipse MAT: https://www.eclipse.org/mat/
# 2. 打开 heap_live.hprof
# 3. 查看 Leak Suspects Report(内存泄漏疑点)
# 4. 查看 Dominator Tree(支配树,找最大对象)
# 5. 查看 Histogram(对象直方图)
4.4 对象类型代码
| 代码 | 类型 |
|---|---|
| B | byte |
| C | char |
| D | double |
| F | float |
| I | int |
| J | long |
| Z | boolean |
| [ | 数组 |
| L | 对象 |
4.5 内存使用指标
| 区域 | 健康 | 警告 | 危险 |
|---|---|---|---|
| Eden使用率 | <80% | 80-95% | >95% |
| 老年代使用率 | <70% | 70-85% | >85% |
| 元空间使用率 | <80% | 80-90% | >90% |
| 堆总使用率 | <75% | 75-90% | >90% |
第五章:jconsole - 图形化监控
5.1 启动和连接
启动JConsole
# 方法1:直接启动JConsole
jconsole
# 方法2:指定Java进程
jconsole PID
# 方法3:指定主机和端口
jconsole hostname:port
# 方法4:通过JAVA_HOME启动
$JAVA_HOME/bin/jconsole
连接方式
# 本地连接(推荐)
1. 启动JConsole
2. 选择"本地进程"
3. 选择目标Java进程
4. 点击"连接"
# 远程连接
1. 启动JConsole
2. 选择"远程进程"
3. 输入:hostname:port
4. 点击"连接"
# 远程连接需要JMX配置
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
5.2 主要功能模块
Overview(概览)
功能:显示JVM基本信息和关键指标
包含:
✅ 堆内存使用情况
✅ 非堆内存使用情况
✅ 线程数量
✅ 类加载数量
✅ CPU使用率
✅ GC信息
Memory(内存)
功能:详细的内存使用情况监控
包含:
✅ 堆内存各区域使用情况
✅ 非堆内存使用情况
✅ 内存使用趋势图
✅ 垃圾回收信息
区域说明:
- Eden Space:新生代Eden区
- Survivor Space:新生代Survivor区
- Old Gen:老年代
- Metaspace:元空间(JDK 8+)
- Code Cache:代码缓存
- Compressed Class Space:压缩类空间
Threads(线程)
功能:线程状态监控和管理
包含:
✅ 线程数量统计
✅ 线程状态分布
✅ 线程详细信息
✅ 死锁检测
✅ 线程堆栈信息
线程状态:
- RUNNABLE:运行中
- BLOCKED:阻塞
- WAITING:等待
- TIMED_WAITING:超时等待
- TERMINATED:已终止
Classes(类)
功能:类加载情况监控
包含:
✅ 已加载类数量
✅ 已卸载类数量
✅ 类加载趋势
✅ 类加载器信息
VM Summary(虚拟机摘要)
功能:JVM配置和运行信息
包含:
✅ JVM版本信息
✅ 启动参数
✅ 系统属性
✅ 运行时间
✅ 编译信息
MBean(管理Bean)
功能:JMX管理功能
包含:
✅ 内存管理
✅ 垃圾回收管理
✅ 线程管理
✅ 类加载管理
✅ 自定义MBean
5.3 使用技巧
内存监控技巧
# 1. 监控堆内存使用趋势
- 观察Eden Space的使用情况
- 关注Old Gen的增长趋势
- 监控Survivor Space的波动
# 2. 内存泄漏检测
- 观察Old Gen是否持续增长
- 检查GC后内存是否释放
- 关注Metaspace的使用情况
# 3. 内存优化建议
- 如果Eden Space频繁满,考虑增加新生代大小
- 如果Old Gen增长过快,检查对象生命周期
- 如果Metaspace使用过多,检查类加载情况
线程监控技巧
# 1. 死锁检测
- 查看"检测死锁"按钮
- 死锁线程会显示为红色
- 点击查看死锁详情
# 2. 线程状态分析
- RUNNABLE线程过多:可能CPU使用率高
- BLOCKED线程过多:可能存在锁竞争
- WAITING线程过多:可能等待资源
# 3. 线程堆栈分析
- 双击线程查看堆栈信息
- 关注阻塞的线程
- 分析线程等待的原因
第六章:其他诊断工具
6.1 jps - Java进程查看
基本用法
# 列出所有Java进程
jps
# 列出所有Java进程的详细信息
jps -l
# 列出所有Java进程的完整信息
jps -v
# 列出所有Java进程的完整信息
jps -m
输出示例
$ jps -l
12345 com.example.Application
67890 org.springframework.boot.loader.JarLauncher
6.2 jinfo - JVM参数查看和修改
基本用法
# 查看JVM参数
jinfo <pid>
# 查看特定JVM参数
jinfo -flag MaxHeapSize <pid>
# 动态修改JVM参数
jinfo -flag +PrintGC <pid>
输出示例
$ jinfo 12345
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.1+13-LTS
Java System Properties:
java.vm.name = OpenJDK 64-Bit Server VM
java.vm.version = 11.0.1+13-LTS
java.vm.vendor = Oracle Corporation
6.3 jcmd - 综合诊断工具
基本用法
# 列出所有可用的命令
jcmd <pid> help
# 生成堆转储文件
jcmd <pid> GC.run_finalization
# 生成线程转储文件
jcmd <pid> Thread.print
# 生成类直方图
jcmd <pid> GC.class_histogram
# 查看JVM参数
jcmd <pid> VM.flags
# 查看系统属性
jcmd <pid> VM.system_properties
# 查看命令行参数
jcmd <pid> VM.command_line
# 查看JVM版本信息
jcmd <pid> VM.version
# 查看JVM启动时间
jcmd <pid> VM.uptime
第七章:线上问题诊断
7.1 死锁检测
7.1.1 现象判别
- 业务线程数正常但接口永久阻塞;日志停在某一步骤不再推进。
- top/监控中 CPU 反而偏低,线程数稳定但无进展。
jstack多次抓取相同线程都卡在BLOCKED (on object monitor),堆栈重复。
7.1.2 快速定位流程
top -H -p <pid>或 Arthasthread -n 5初步查看是否有大量线程处于BLOCKED。jstack <pid> | grep -A 30 <hexTid>定位互相等待的线程与锁;或 Arthasthread -b输出阻塞线程。jconsole线程面板的“检测死锁”可一键验证,死锁线程会标红。- 若确认死锁,保留堆栈/日志,排查代码中的双重锁序、嵌套锁或资源竞争。
7.1.3 常见根因方向
- 互斥锁获取顺序不一致(线程A先锁资源1再锁资源2,线程B顺序相反)
- 嵌套锁/递归锁未妥善释放,导致线程间循环等待
- 数据库连接、分布式锁等外部资源获取顺序冲突
synchronized与显式锁(ReentrantLock、ReadWriteLock等)混合使用不当- 异常路径缺失
finally解锁,或调用wait/notify时持锁不释放
实时死锁检测
# 1. 使用jstack检测死锁
jstack PID | grep -A 10 -B 10 "deadlock"
# 2. 使用jcmd检测死锁
jcmd PID Thread.print | grep -A 20 -B 5 "deadlock"
# 3. 使用jconsole检测
# 启动jconsole,连接进程,查看"线程"标签页
# 死锁线程会显示为红色
死锁检测脚本
#!/bin/bash
# deadlock-detector.sh
PID=$1
if [ -z "$PID" ]; then
echo "Usage: $0 <PID>"
exit 1
fi
echo "检测进程 $PID 的死锁情况..."
# 检测死锁
DEADLOCK_INFO=$(jstack $PID | grep -A 20 "Found Java-level deadlock")
if [ -n "$DEADLOCK_INFO" ]; then
echo "🚨 发现死锁!"
echo "$DEADLOCK_INFO"
# 发送告警
curl -X POST "webhook_url" -d "{
\"text\": \"发现死锁: PID=$PID\",
\"deadlock_info\": \"$DEADLOCK_INFO\"
}"
else
echo "✅ 未发现死锁"
fi
# 检测线程阻塞
BLOCKED_THREADS=$(jstack $PID | grep "BLOCKED" | wc -l)
if [ $BLOCKED_THREADS -gt 10 ]; then
echo "⚠️ 发现 $BLOCKED_THREADS 个阻塞线程,可能存在死锁风险"
fi
7.2 CPU高检测
7.2.1 现象判别
- 监控/
top显示 Java 进程%CPU持续高位,系统负载升高。 - 单条线程 CPU 飙高:
top -H -p <pid>发现某 TID 长期占 90%+。 - GC 线程占用高时需结合 GC 日志确认是否因频繁 GC。
7.2.2 快速定位流程
top -H -p <pid>或ps -Lp <pid>找到占用高的线程 ID,转换为 16 进制(printf "%x\n" <tid>)。jstack <pid> | grep -A 30 <hexTid>查看堆栈,定位具体代码行;或 Arthasthread -n 5、thread <tid>。- 使用 Arthas 深入:
trace com.xxx.Service method "#cost > 100"追踪高耗时方法。watch com.xxx.Service method "{params,#cost}"观察入参/耗时。profiler start→profiler stop --format html生成 CPU 火焰图。
- 结合业务逻辑排查:死循环、锁自旋、热点 SQL/远程调用、多线程竞争等。
7.2.3 常见根因方向
- 代码逻辑上的死循环、忙轮询、递归无退出
- 热点算法/正则回溯/字符串拼接造成高 CPU 计算
- 锁竞争或 CAS 自旋导致线程长时间空转
- GC 频繁(参数不当或对象激增)引发 GC 线程占用 CPU
- 外部接口/数据库耗时长,工作线程大量堆积在 RUNNABLE
CPU使用率监控
# 1. 实时监控CPU使用率
top -p PID -n 1 | grep PID
# 2. 监控Java进程CPU使用率
ps -p PID -o pid,ppid,cmd,%cpu,%mem
# 3. 使用htop查看详细CPU信息
htop -p PID
CPU高问题诊断脚本
#!/bin/bash
# cpu-monitor.sh
PID=$1
THRESHOLD=80 # CPU使用率阈值
if [ -z "$PID" ]; then
echo "Usage: $0 <PID>"
exit 1
fi
while true; do
# 获取CPU使用率
CPU_USAGE=$(ps -p $PID -o %cpu --no-headers | tr -d ' ')
if (( $(echo "$CPU_USAGE > $THRESHOLD" | bc -l) )); then
echo "$(date): 🚨 CPU使用率过高: ${CPU_USAGE}%"
# 生成线程堆栈
jstack $PID > "jstack-$(date +%Y%m%d-%H%M%S).txt"
# 分析CPU热点
echo "=== CPU热点分析 ==="
jstack $PID | grep -A 5 -B 5 "RUNNABLE" | head -20
# 发送告警
curl -X POST "webhook_url" -d "{
\"text\": \"CPU使用率过高: ${CPU_USAGE}%\",
\"pid\": \"$PID\",
\"timestamp\": \"$(date)\"
}"
fi
sleep 10
done
7.3 内存高检测
7.3.1 现象判别
- 监控中堆使用率持续拉高,GC 后下降不明显,Full GC 频率增加。
- 触发 OOM 前 RSS/堆曲线呈“锯齿向上”;
jstat -gcutilOld Gen 值逐步逼近 100%。 - Arthas
memory、jmap -heap显示老年代接近满载。
7.3.2 快速定位流程
jstat -gcutil <pid> 1000观察 Eden/Old 使用率与 GC 次数,判断是“瞬时膨胀”还是“无法回收”。jmap -histo <pid>连续执行两次,比较对象 Top 列表是否持续增长。jmap -dump:live或 Arthasheapdump导出堆,配合 MAT/VisualVM/HeapHero 分析 Dominator Tree、Leak Suspects。- 使用 Arthas 辅助:
vmtool --action getInstances --className xxx查看实例数。watch/trace跟踪可疑方法的对象创建。
- 根据结果采取措施:清理缓存/集合、修复 ThreadLocal、检查大对象生命周期、优化分配模式。
7.3.3 常见根因方向
- 静态集合、缓存、Map 无上限增长,缺少过期/淘汰策略
- 短时间突发流量或批处理任务创建大量对象导致堆膨胀
- 线程池/消息队列堆积,任务数据在内存中等待处理
- 序列化/反序列化、大文件读取一次性加载全部数据
- GC 参数配置不当(Survivor 太小、晋升阈值低)导致老年代被挤爆
内存使用率监控
# 1. 监控堆内存使用率
jstat -gc PID 1s 5
# 2. 监控进程内存使用
ps -p PID -o pid,ppid,cmd,%mem,rss,vsz
# 3. 监控系统内存使用
free -h
内存高问题诊断脚本
#!/bin/bash
# memory-monitor.sh
PID=$1
THRESHOLD=85 # 内存使用率阈值
if [ -z "$PID" ]; then
echo "Usage: $0 <PID>"
exit 1
fi
while true; do
# 获取堆内存使用率
HEAP_USAGE=$(jstat -gc $PID | tail -1 | awk '{print ($3+$4+$6+$8)/($1+$2+$5+$7)*100}')
if (( $(echo "$HEAP_USAGE > $THRESHOLD" | bc -l) )); then
echo "$(date): 🚨 堆内存使用率过高: ${HEAP_USAGE}%"
# 生成堆转储
jmap -dump:live,format=b,file="heap-$(date +%Y%m%d-%H%M%S).hprof" $PID
# 分析内存使用
echo "=== 内存使用分析 ==="
jmap -histo $PID | head -20
# 发送告警
curl -X POST "webhook_url" -d "{
\"text\": \"堆内存使用率过高: ${HEAP_USAGE}%\",
\"pid\": \"$PID\",
\"timestamp\": \"$(date)\"
}"
fi
sleep 30
done
7.4 内存泄漏检测
7.4.1 现象判别
- GC 后 Old Gen 仍不下降,
jstat多次输出 OG 使用率逐渐逼近 100%。 - 日志出现
java.lang.OutOfMemoryError之前伴随 Full GC、频繁Promotion failed。 - MAT/HeapHero 报告中存在长时间不释放的大对象或静态集合。
7.4.2 快速定位流程
- 先确认现象:
jstat -gcutil <pid>、监控平台堆曲线、GC 日志。 - 导出堆:
jmap -dump:live,format=b,file=heap.hprof <pid>(注意对线上性能影响)。 - 使用 MAT 分析:
- Leak Suspects 报告、Dominator Tree 找“谁持有最多内存”。
- Path to GC Roots 分析引用链(常见为静态集合、缓存、ThreadLocal、监听器)。
- 在线追踪:
- Arthas
watch com.xxx.Service create "{params,returnObj}"观察对象创建频次。 profiler start --event alloc捕获分配热点。
- Arthas
- 制定修复方案:清理缓存/集合、增加过期策略、确保资源关闭、避免泄漏型单例。
7.4.3 常见根因方向
- 缓存、集合、Session 等容器持续增长,无淘汰清理
- ThreadLocal 使用后未
remove,在线程复用场景中残留引用 - 监听器/回调注册后未注销,导致对象被长期持有
- 单例或静态变量持有外部资源(上下文、连接、对象图)
- 自定义类加载器/动态代理反复生成类,导致
ClassLoader泄漏
内存泄漏检测脚本
#!/bin/bash
# memory-leak-detector.sh
PID=$1
if [ -z "$PID" ]; then
echo "Usage: $0 <PID>"
exit 1
fi
# 生成两个时间点的堆转储
echo "生成第一个堆转储..."
jmap -dump:live,format=b,file="heap-before-$(date +%Y%m%d-%H%M%S).hprof" $PID
echo "等待5分钟..."
sleep 300
echo "生成第二个堆转储..."
jmap -dump:live,format=b,file="heap-after-$(date +%Y%m%d-%H%M%S).hprof" $PID
# 分析内存增长
echo "=== 内存增长分析 ==="
jmap -histo $PID | head -20
# 检查GC情况
echo "=== GC情况分析 ==="
jstat -gc $PID
第八章:实战案例
8.1 案例1:应用响应慢
症状:
- 接口响应时间从100ms增加到5s
- 用户投诉系统卡顿
排查步骤:
# 1. 检查CPU使用率
top -p <pid>
# 如果CPU很高 → 去案例2
# 如果CPU正常,IO高 → 可能是数据库或网络问题
# 如果CPU和IO都正常 → 可能是线程阻塞
# 2. 查看线程状态
jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c
# 输出:
# 50 RUNNABLE
# 200 WAITING
# 80 BLOCKED ← 大量阻塞!
# 20 TIMED_WAITING
# 3. 查看阻塞的线程
jstack <pid> | grep -A 10 "BLOCKED"
# 找到被阻塞的线程和等待的锁
# 4. 分析锁竞争
jstack -l <pid> > thread_dump.txt
# 查看哪个线程持有锁,为什么不释放
8.2 案例2:CPU飙高
症状:
- CPU使用率持续90%以上
- 应用响应缓慢
排查步骤:
# 1. 找到高CPU线程
top -H -p <pid>
# 记录TID(如12346)
# 2. 转换为16进制
printf "%x\n" 12346
# 输出: 303a
# 3. 查看该线程堆栈
jstack <pid> | grep -A 30 303a
# 4. 定位代码
# 在堆栈中找到自己的代码
at com.example.service.UserService.queryUsers(UserService.java:45)
# ↑ 第45行
# 5. 分析代码
# - 是否有死循环
# - 是否有大量计算
# - 是否有正则表达式回溯
# - 是否有大量字符串拼接
8.3 案例3:频繁Full GC
症状:
- 应用频繁卡顿
- 日志中大量Full GC
排查步骤:
# 1. 监控GC(持续5分钟)
jstat -gcutil <pid> 1000 300 > gc.log
# 2. 分析gc.log
# 关注:
# - FGC列是否快速增长
# - O(老年代)使用率是否持续很高
# - FGCT(Full GC总耗时)是否占比很大
# 3. 查看堆配置
jmap -heap <pid>
# 检查堆大小是否合理
# 4. 查看对象分布
jmap -histo <pid> | head -30
# 找出占用内存最多的对象
# 5. 生成堆转储分析
jmap -dump:live,format=b,file=heap.hprof <pid>
# 6. 使用MAT分析
# - 查看 Leak Suspects(泄漏疑点)
# - 查看 Dominator Tree(找大对象)
# - 查看 Path to GC Roots(找引用链)
8.4 案例4:内存溢出
症状:
- 应用崩溃
- 日志:
java.lang.OutOfMemoryError: Java heap space
排查步骤:
# 如果配置了 -XX:+HeapDumpOnOutOfMemoryError
# 会自动生成heap dump文件
# 1. 分析heap dump
# 使用MAT打开自动生成的hprof文件
# 2. 如果没有自动dump,手动生成
jmap -dump:format=b,file=oom.hprof <pid>
# 3. MAT分析步骤
# a. 打开Overview -> Leak Suspects
# b. 查看占用最多内存的对象
# c. 右键 -> Path to GC Roots -> exclude weak references
# d. 找到持有对象的引用链
# e. 分析为什么对象无法被回收
# 4. 常见原因
# - 集合类持续增长未清理
# - 缓存无过期策略
# - ThreadLocal未清理
# - 监听器未移除
# - 静态集合持有大量对象
第九章:监控脚本
9.1 一键诊断脚本
#!/bin/bash
# jvm_diagnosis.sh - JVM一键诊断脚本
PID=$1
if [ -z "$PID" ]; then
echo "Usage: $0 <pid>"
exit 1
fi
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
OUTPUT_DIR="jvm_diagnosis_${PID}_${TIMESTAMP}"
mkdir -p $OUTPUT_DIR
echo "开始诊断 PID: $PID"
echo "输出目录: $OUTPUT_DIR"
# 1. 基本信息
echo "1. 收集基本信息..."
jps -lvm > $OUTPUT_DIR/jps.txt
jinfo -flags $PID > $OUTPUT_DIR/jinfo.txt
# 2. 线程信息
echo "2. 收集线程信息..."
jstack -l $PID > $OUTPUT_DIR/jstack.txt
jstack $PID | grep "java.lang.Thread.State" | sort | uniq -c > $OUTPUT_DIR/thread_state_summary.txt
# 3. GC信息
echo "3. 收集GC信息(30秒)..."
jstat -gcutil $PID 1000 30 > $OUTPUT_DIR/jstat_gcutil.txt
# 4. 堆信息
echo "4. 收集堆信息..."
jmap -heap $PID > $OUTPUT_DIR/jmap_heap.txt
jmap -histo $PID | head -100 > $OUTPUT_DIR/jmap_histo.txt
# 5. 检查死锁
echo "5. 检查死锁..."
grep -A 30 "Found.*deadlock" $OUTPUT_DIR/jstack.txt > $OUTPUT_DIR/deadlock.txt
# 6. 生成报告
echo "6. 生成诊断报告..."
cat > $OUTPUT_DIR/README.txt <<EOF
JVM诊断报告
==========
PID: $PID
时间: $TIMESTAMP
文件说明:
- jps.txt: Java进程信息
- jinfo.txt: JVM参数配置
- jstack.txt: 线程堆栈信息
- thread_state_summary.txt: 线程状态统计
- jstat_gcutil.txt: GC统计(30秒)
- jmap_heap.txt: 堆内存配置
- jmap_histo.txt: 对象分布Top100
- deadlock.txt: 死锁信息(如果有)
下一步:
1. 查看 thread_state_summary.txt 了解线程分布
2. 查看 deadlock.txt 检查是否有死锁
3. 查看 jstat_gcutil.txt 分析GC情况
4. 查看 jmap_histo.txt 分析内存使用
如需更详细分析,可以执行:
jmap -dump:live,format=b,file=heap.hprof $PID
EOF
echo "诊断完成!请查看目录: $OUTPUT_DIR"
ls -lh $OUTPUT_DIR/
9.2 综合监控脚本
#!/bin/bash
# comprehensive-monitor.sh
PID=$1
LOG_DIR="/var/log/jvm-monitor"
mkdir -p $LOG_DIR
while true; do
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
echo "=== JVM 监控报告 - $TIMESTAMP ===" >> $LOG_DIR/jvm_monitor.log
# 内存使用情况
echo "--- 内存使用情况 ---" >> $LOG_DIR/jvm_monitor.log
jstat -gc $PID 1s 1 >> $LOG_DIR/jvm_monitor.log
# 线程状态
echo "--- 线程状态 ---" >> $LOG_DIR/jvm_monitor.log
jstack $PID | grep "java.lang.Thread.State" | sort | uniq -c >> $LOG_DIR/jvm_monitor.log
# 类加载情况
echo "--- 类加载情况 ---" >> $LOG_DIR/jvm_monitor.log
jstat -class $PID 1s 1 >> $LOG_DIR/jvm_monitor.log
echo "" >> $LOG_DIR/jvm_monitor.log
sleep 60
done
9.3 告警脚本
#!/bin/bash
# alert-monitor.sh
PID=$1
THRESHOLD_CPU=80
THRESHOLD_MEMORY=85
THRESHOLD_GC=10
while true; do
# CPU检查
CPU_USAGE=$(ps -p $PID -o %cpu --no-headers | tr -d ' ')
if (( $(echo "$CPU_USAGE > $THRESHOLD_CPU" | bc -l) )); then
echo "$(date): CPU告警 - ${CPU_USAGE}%" | tee -a alert.log
fi
# 内存检查
HEAP_USAGE=$(jstat -gc $PID | tail -1 | awk '{print ($3+$4+$6+$8)/($1+$2+$5+$7)*100}')
if (( $(echo "$HEAP_USAGE > $THRESHOLD_MEMORY" | bc -l) )); then
echo "$(date): 内存告警 - ${HEAP_USAGE}%" | tee -a alert.log
fi
# GC检查
YGC_COUNT=$(jstat -gc $PID | tail -1 | awk '{print $13}')
if [ $YGC_COUNT -gt $THRESHOLD_GC ]; then
echo "$(date): GC告警 - ${YGC_COUNT}次" | tee -a alert.log
fi
sleep 30
done
第十章:命令速查表
10.1 进程管理命令
# 查看Java进程
jps -lvm
# 查看JVM参数
jinfo <pid>
# 查看特定JVM参数
jinfo -flag MaxHeapSize <pid>
# 动态修改JVM参数
jinfo -flag +PrintGC <pid>
10.2 内存监控命令
# GC监控
jstat -gc <pid> 1s 60
jstat -gcutil <pid> 1s 60
jstat -gccause <pid> 1s 60
# 内存分析
jmap -heap <pid>
jmap -histo <pid> | head -30
jmap -histo:live <pid> | head -30
# 堆转储
jmap -dump:live,format=b,file=heap.hprof <pid>
10.3 线程监控命令
# 线程转储
jstack <pid> > threaddump.txt
jstack -l <pid> > threaddump.txt
# 死锁检测
jstack <pid> | grep -A 30 "deadlock"
# 线程状态统计
jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c
10.4 综合诊断命令
# jcmd综合命令
jcmd <pid> help
jcmd <pid> Thread.print
jcmd <pid> GC.class_histogram
jcmd <pid> VM.flags
jcmd <pid> VM.system_properties
10.5 常用排查命令组合
快速诊断
# 获取进程信息
jps -l
# 查看JVM参数
jinfo <pid>
# 监控内存使用
jstat -gc <pid> 1s 5
# 查看线程状态
jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c
深度诊断
# 生成堆转储
jmap -dump:live,format=b,file=heap.hprof <pid>
# 生成线程转储
jstack <pid> > threaddump.txt
# 生成类直方图
jmap -histo:live <pid> > class_histogram.txt
# 查看GC信息
jstat -gc <pid> 1s 10 > gc_info.txt
10.6 常见问题速查
| 问题 | 使用工具 | 关键命令 |
|---|---|---|
| CPU飙高 | jstack | top -H + jstack |
| 内存泄漏 | jmap | jmap -histo + dump |
| 频繁GC | jstat | jstat -gcutil |
| 死锁 | jstack | jstack -l |
| OOM | jmap | jmap -heap + dump |
| 响应慢 | jstack | jstack(查看BLOCKED) |
| 线程过多 | jstack | jstack | grep “java.lang.Thread.State” |
总结
JVM诊断工具使用原则
- 问题导向:根据问题类型选择合适的工具
- 组合使用:多个工具配合使用,全面分析
- 持续监控:建立监控体系,及时发现问题
- 数据驱动:基于监控数据做调优决策
工具选择建议
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 日常监控 | jconsole | 图形化界面,易于使用 |
| 问题排查 | jstack + jstat + jmap | 命令行工具,功能强大 |
| 深度分析 | MAT + JProfiler | 专业分析工具 |
| 自动化监控 | 脚本 + 告警 | 持续监控,及时响应 |
最佳实践
- 建立监控基线:记录正常情况下的指标
- 设置告警阈值:及时发现异常情况
- 定期健康检查:使用诊断脚本定期检查
- 问题记录分析:记录问题排查过程和解决方案
- 持续优化改进:根据监控数据持续优化
记住:
- jstack看线程(死锁、阻塞、CPU高)
- jstat看GC(频率、耗时、使用率)
- jmap看内存(配置、对象、泄漏)
- jconsole看整体(图形化监控)
通过掌握这些诊断工具,可以有效地解决JVM线上问题,提高应用性能和稳定性!
3万+

被折叠的 条评论
为什么被折叠?



