Java排查问题案例

wen java案例 1

本文目录导读:

Java排查问题案例

  1. CPU使用率过高排查
  2. 内存溢出(OOM)排查
  3. 死锁排查
  4. 数据库连接池耗尽
  5. 线程池问题排查
  6. 性能瓶颈分析
  7. 完整排查工具集
  8. 最佳实践建议

我将为您系统性地介绍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

常见原因和解决方案

  1. 死循环
    // 问题代码示例
    while(true) {
     // 业务逻辑
    }

// 排查方法:线程栈会显示在一个方法内反复执行


2. **频繁GC导致CPU飙升**
```bash
# 查看GC情况
jstat -gcutil <PID> 1000 10
  1. 正则表达式回溯
    // 危险的正则表达式导致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应用中的各种问题,好的监控和日志是有效排查的基础。

抱歉,评论功能暂时关闭!