本文目录导读:

我将为您系统性地介绍Java问题排查的实战案例,从常见问题到具体排查方法,每个案例都包含详细的诊断思路和解决方案。
CPU使用率过高排查
案例1:CPU飙升至100%
问题现象:应用响应缓慢,CPU使用率持续在100%
排查步骤:
找到CPU最高的进程
# 查看所有Java进程CPU使用率 top -c # 或使用更详细的查看方式 ps -eo pid,ppid,%mem,%cpu,cmd --sort=-%cpu | head -20
定位到问题的线程
# 找到CPU使用最高的线程 top -Hp <PID> # 查看线程信息 ps -mp <PID> -o THREAD,tid,time
将线程ID转换为十六进制
printf "%x\n" <TID>
导出线程栈信息
jstack <PID> > thread_dump.txt # 在dump文件中查找对应线程 grep -A 30 "0x<十六进制TID>" thread_dump.txt
常见原因和解决方案:
- 死循环
// 问题代码示例 while(true) { // 业务逻辑 }
// 排查方法:线程栈会显示在一个方法内反复执行
2. **频繁GC导致CPU飙升**
```bash
# 查看GC情况
jstat -gcutil <PID> 1000 10
- 正则表达式回溯
// 危险的正则表达式导致CPU高 String pattern = "(a+)+$";
内存溢出(OOM)排查
案例2:Java堆内存溢出
问题现象:报错java.lang.OutOfMemoryError: Java heap space
排查步骤:
启动时添加参数
java -Xms512m -Xmx512m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump/
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/path/to/gc.log
使用MAT分析堆转储文件
# 下载并打开堆转储文件 jhat <heap-dump-file> # 或使用Eclipse MAT # File -> Open Heap Dump
典型OOM场景分析
内存泄漏示例:
public class MemoryLeak {
private static List<byte[]> LEAK = new ArrayList<>();
public void leak() {
while(true) {
byte[] bytes = new byte[1024 * 1024]; // 1MB
LEAK.add(bytes); // 永远不会被回收
}
}
}
解决方案:
- 使用
-Xmx参数调整堆大小 - 优化代码,减少内存占用
- 使用缓存时设置合理的过期策略
- 使用
-XX:+UseG1GC等垃圾回收器优化
案例3:Metaspace溢出
问题现象:java.lang.OutOfMemoryError: Metaspace
原因分析:
- 动态生成大量类
- 使用cglib/ASM等字节码技术
- 不正确的类加载器使用
解决方案:
# 调整Metaspace大小 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
死锁排查
案例4:多线程死锁
问题现象:程序卡住,无响应
排查步骤:
获取线程dump
jstack <PID> > thread_dump.txt
查找死锁信息
# 在dump文件中查找明显标记 grep -A 20 "Found one Java-level deadlock" thread_dump.txt
死锁示例:
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread1 acquired lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread1 acquired lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread2 acquired lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread2 acquired lock1");
}
}
});
t1.start();
t2.start();
}
}
解决方案:
- 使用定时锁
tryLock()避免死锁 - 保持锁顺序一致性
- 使用并发工具类如
ReentrantLock
数据库连接池耗尽
案例5:连接池连接无法释放
问题现象:报错Cannot get a connection, pool exhausted
排查步骤:
检查连接池配置
# Spring Boot配置示例
spring:
datasource:
hikari:
maximum-pool-size: 50
connection-timeout: 30000
leak-detection-threshold: 60000
启用连接泄漏检测
// HikariCP配置
HikariConfig config = new HikariConfig();
config.setLeakDetectionThreshold(60000); // 60秒
config.setPoolName("MyPool");
检查代码模式
// 错误示例 - 连接未关闭
public void badQuery() throws SQLException {
Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
// 忘记关闭连接!
}
// 正确示例 - 使用try-with-resources
public void goodQuery() throws SQLException {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
// 处理结果
}
}
线程池问题排查
案例6:线程池拒绝策略
问题现象:报错RejectedExecutionException
排查步骤:
检查线程池状态
# 使用jstack查看线程池状态 jstack <PID> | grep -A 10 "ThreadPoolExecutor"
分析代码
// 问题代码示例
ExecutorService executor = Executors.newFixedThreadPool(10);
// 任务提交速度过快,队列满了
// 解决方案:选择适当的拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // 核心线程数
20, // 最大线程数
60, TimeUnit.SECONDS, // 空闲线程存活时间
new ArrayBlockingQueue<>(100), // 队列容量
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
性能瓶颈分析
案例7:频繁Full GC
问题现象:大量Full GC,应用卡顿
排查步骤:
查看GC日志
jstat -gcutil <PID> 1000 # 或查看GC日志文件 grep "Full GC" gc.log
使用VisualVM/JMC分析
# 使用jcmd查看GC信息 jcmd <PID> GC.heap_info jcmd <PID> VM.native_memory
优化方案
# G1收集器优化示例 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 -Xmx4g
完整排查工具集
JDK自带工具
jps # 查看Java进程 jstat # 查看JVM统计信息 jstack # 线程转储 jmap # 内存映射 jhat # 堆分析工具 jcmd # JVM命令工具 jinfo # JVM配置信息
常用命令示例
# 查看JVM参数 jcmd <PID> VM.print_compilation # 查看GC配置 jcmd <PID> GC.class_histogram # 导出堆快照 jmap -dump:format=b,file=heap.hprof <PID> # 查看线程状态分布 jstack <PID> | grep "java.lang.Thread.State" | sort | uniq -c
排查脚本示例
#!/bin/bash # auto_diagnose.sh PID=$1 echo "=== 1. System Load ===" top -b -n 1 | head -5 echo "=== 2. Memory Usage ===" free -m echo "=== 3. Java Process Info ===" jcmd $PID VM.version jcmd $PID VM.flags echo "=== 4. Thread Dump ===" jstack $PID > thread_dump.txt echo "=== 5. Heap Summary ===" jmap -heap $PID echo "=== 6. GC Statistics ===" jstat -gcutil $PID 1000 5
最佳实践建议
预防措施
- 开发环境启用
-Xmx,-Xms调优 - 生产环境启用GC日志和HeapDumpOnOutOfMemoryError
- 使用APM工具监控(如SkyWalking, Zipkin)
监控告警
# Prometheus + Grafana监控配置示例
- job_name: 'jvm'
static_configs:
- targets: ['localhost:8080']
metrics_path: '/actuator/prometheus'
常见错误处理清单
| 症状 | 可能原因 | 排查方法 |
|---|---|---|
| CPU高 | 死循环、GC频繁 | jstack + jstat |
| 内存溢出 | OOM、内存泄漏 | jmap + MAT |
| 响应慢 | 网络、IO、锁竞争 | jstack + JMC |
| 连接耗尽 | 连接不释放、配置不当 | 连接池监控 |
通过这些案例和工具的使用,您可以系统地排查Java应用中的各种问题,好的监控和日志是有效排查的基础。