故障排查详解
故障排查详解本章导读系统故障不可避免,但快速定位和解决问题的能力决定了系统的可用性。本章系统讲解OOM、CPU飙升、死锁等常见故障的排查方法与工具使用,帮助读者建立完整的故障排查体系,从"盲人摸象"进化到"精准定位"。学习目标:目标1:掌握JDK诊断工具(jstack、jmap、jstat)的使用方法目标2:能够分析堆转储文件定位内存泄漏根因目标3:掌握CPU飙升问题的定位流程与常见原因分析目标4:能够检测和诊断线程死锁并制定预防策略目标5:建立系统化的故障排查思维框架与应急预案前置知识:熟悉JVM内存模型与GC机制,了解多线程编程基础,具备Linux运维经验阅读时长:约 50 分钟一、知识概述故障排查是保障系统稳定运行的核心能力。当系统出现异常时,能够快速定位问题根因并采取有效措施,是每个开发和运维人员必备的技能。本文将详细介绍Java应用常见故障的排查方法和工具使用。1.1 故障排查体系┌─────────────────────────────────────────────────────────────┐ │ 故障排查体系 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 故障发现 │ │ └── 监控告警、用户反馈、日志异常 │ │ │ │ 2. 故障定位 │ │ └── 分析症状、收集信息、定位原因 │ │ │ │ 3. 故障处理 │ │ └── 临时方案、根本解决、验证效果 │ │ │ │ 4. 故障复盘 │ │ └── 总结原因、改进措施、文档沉淀 │ │ │ │ 常见故障类型: │ │ ┌─────────────┬─────────────────────────────────────┐ │ │ │ OOM │ 内存溢出,应用崩溃 │ │ │ ├─────────────┼─────────────────────────────────────┤ │ │ │ CPU飙升 │ CPU占用过高,响应缓慢 │ │ │ ├─────────────┼─────────────────────────────────────┤ │ │ │ 死锁 │ 线程阻塞,功能不可用 │ │ │ ├─────────────┼─────────────────────────────────────┤ │ │ │ 响应慢 │ 接口超时,用户体验差 │ │ │ ├─────────────┼─────────────────────────────────────┤ │ │ │ 连接异常 │ 数据库/缓存连接失败 │ │ │ └─────────────┴─────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘1.2 故障排查工具箱┌─────────────────────────────────────────────────────────────┐ │ 故障排查工具箱 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ JDK工具 │ │ ├── jps - 查看Java进程 │ │ ├── jstat - JVM统计信息 │ │ ├── jinfo - 查看JVM配置 │ │ ├── jmap - 内存映射、堆转储 │ │ ├── jstack - 线程堆栈 │ │ ├── jcmd - 多功能诊断工具 │ │ └── jconsole - 图形化监控 │ │ │ │ 系统工具 │ │ ├── top/htop - 系统资源监控 │ │ ├── vmstat - 虚拟内存统计 │ │ ├── iostat - IO统计 │ │ ├── netstat - 网络连接 │ │ ├── lsof - 打开的文件 │ │ ├── strace - 系统调用跟踪 │ │ └── perf - 性能分析 │ │ │ │ 高级工具 │ │ ├── Arthas - 阿里开源诊断工具 │ │ ├── JProfiler - 商业性能分析工具 │ │ ├── MAT - 内存分析工具 │ │ └── async-profiler - 异步性能分析 │ │ │ └─────────────────────────────────────────────────────────────┘二、OOM分析与排查2.1 OOM类型与原因┌─────────────────────────────────────────────────────────────┐ │ OOM类型与原因 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. Java heap space │ │ 原因:堆内存不足,对象过多无法回收 │ │ 解决:增大堆内存、分析内存泄漏、优化代码 │ │ │ │ 2. Metaspace / PermGen │ │ 原因:类加载过多、动态代理、反射使用过度 │ │ 解决:增大元空间、限制类加载、优化框架配置 │ │ │ │ 3. GC overhead limit exceeded │ │ 原因:GC时间过长,无法有效回收内存 │ │ 解决:优化GC参数、分析内存泄漏 │ │ │ │ 4. Direct buffer memory │ │ 原因:NIO直接内存不足 │ │ 解决:增大直接内存限制、检查NIO使用 │ │ │ │ 5. Out of swap space │ │ 原因:系统交换空间不足 │ │ 解决:增加系统内存、调整内存使用 │ │ │ │ 6. Requested array size exceeds VM limit │ │ 原因:请求的数组大小超过JVM限制 │ │ 解决:优化业务逻辑,避免创建超大数组 │ │ │ └─────────────────────────────────────────────────────────────┘2.2 堆内存分析# ============================================# 查看Java进程# ============================================jps-lvm# 输出示例:# 12345 myapp.jar -Xms2g -Xmx2g -XX:+UseG1GC# ============================================# 查看堆内存使用情况# ============================================jstat-gc1234510005# 输出说明:# S0C S1C S0U S1U EC EU OC OU MC MU# 512.0 512.0 0.0 128.0 8192.0 4096.0 16384.0 12288.0 8192.0 7168.0## S0C/S1C: Survivor区容量# S0U/S1U: Survivor区使用量# EC/EU: Eden区容量/使用量# OC/OU: 老年代容量/使用量# MC/MU: 元空间容量/使用量# ============================================# 生成堆转储文件# ============================================# 方式1: 使用jmapjmap-dump:format=b,file=heap.hprof12345# 方式2: 使用jcmdjcmd12345GC.heap_dump /tmp/heap.hprof# 方式3: 应用启动时自动生成(推荐生产环境)# -XX:+HeapDumpOnOutOfMemoryError# -XX:HeapDumpPath=/logs/heapdump.hprof# ============================================# 查看堆内存直方图# ============================================jmap-histo:live12345|head-30# 输出示例:# num #instances #bytes class name# ----------------------------------------------# 1: 50000 4000000 com.example.User# 2: 30000 2400000 java.lang.String# 3: 20000 1600000 java.util.HashMap$Node# ============================================# 使用MAT分析堆转储# ============================================# 1. 下载MAT: https://eclipse.dev/mat/# 2. 打开heap.hprof文件# 3. 查看Dominator Tree(支配树)# 4. 分析Leak Suspects(内存泄漏嫌疑)# 5. 查看Histogram(对象统计)2.3 内存泄漏排查实战// ============================================// 常见内存泄漏场景// ============================================// 场景1: 静态集合持有对象引用publicclassMemoryLeakDemo{// 静态集合会一直持有对象引用,导致无法被GCprivatestaticfinalMapString,ObjectCACHE=newHashMap();publicvoidaddToCache(Stringkey,Objectvalue){CACHE.put(key,value);// 如果不删除,会一直占用内存}// 正确做法:使用弱引用或定期清理privatestaticfinalMapString,ObjectWEAK_CACHE=newWeakHashMap();}// 场景2: 监听器未注销publicclassEventSource{privateListEventListenerlisteners=newArrayList();publicvoidaddListener(EventListenerlistener){listeners.add(listener);}// 忘记提供remove方法,导致监听器无法被回收// public void removeListener(EventListener listener) {// listeners.remove(listener);// }}// 场景3: ThreadLocal未清理publicclassThreadLocalLeak{privatestaticfinalThreadLocalbyte[]THREAD_LOCAL=newThreadLocal();publicvoidprocess(){THREAD_LOCAL.set(newbyte[1024*1024]);// 1MB// 忘记清理// THREAD_LOCAL.remove();}}// 场景4: 数据库连接未关闭publicclassConnectionLeak{publicvoidquery(){Connectionconn=null;try{conn=dataSource.getConnection();// 执行查询}finally{// 忘记关闭连接// if (conn != null) conn.close();}}}// 场景5: 缓存无限增长publicclassCacheLeak{privateMapString,CacheEntrycache=newHashMap();publicvoidput(Stringkey,Objectvalue){cache.put(key,newCacheEntry(value,System.currentTimeMillis()));// 没有清理过期缓存的机制}// 正确做法:使用Caffeine等缓存框架// private CacheString, Object cache = Caffeine.newBuilder()// .maximumSize(10000)// .expireAfterWrite(Duration.ofMinutes(30))// .build();}// ============================================// 内存泄漏排查示例// ============================================// 使用Arthas排查内存泄漏// 1. 启动Arthas// java -jar arthas-boot.jar// 2. 选择目标进程// 选择对应的Java进程编号// 3. 查看内存使用情况// dashboard// 4. 查看对象统计// heap