Java生产环境问题排查实战指南
在生产环境中Java应用可能会遇到各种性能瓶颈和运行时错误如CPU占用过高、内存溢出、频繁Full GC或进程意外退出等。这些问题往往相互关联需要一套系统化的排查方法来快速定位根因并解决。本文将针对几种常见问题提供详细的排查思路、工具和命令。问题一持续出现Full GC频繁的Full GC会严重拖慢应用性能导致系统响应变慢甚至无响应。1. 现象与可能原因现象应用响应时间RT变长吞吐量下降CPU使用率可能因GC线程繁忙而升高。可能原因内存泄漏这是最常见的原因。长生命周期的对象持有了短生命周期对象的引用导致GC无法回收。堆内存设置过小应用实际所需内存超过了JVM堆Heap的最大设置-Xmx。大对象过多频繁创建大数组或大对象直接进入老年代快速填满老年代空间。元空间Metaspace不足动态加载的类过多如使用CGLIB、Groovy等导致元空间被填满触发Full GC。显式调用代码中存在System.gc()的调用。2. 排查步骤第一步确认GC情况如果已开启GC日志-Xloggc:/path/to/gc.log直接分析日志观察Full GC的频率、每次回收后的内存占用情况。如果未开启GC日志可以使用jstat命令实时监控# 每隔1000毫秒输出一次GC信息共输出10次jstat-gcutilPID100010重点关注O老年代使用率和M元空间使用率是否持续很高以及FGCFull GC次数和FGCTFull GC总耗时的增长情况。第二步获取堆内存快照Heap Dump当确认是内存问题后需要获取堆内存快照进行分析。方法A自动Dump推荐在JVM启动参数中添加以下选项当发生OOM时会自动生成dump文件。-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/path/to/dump.hprof方法B手动Dump在问题发生时使用jmap命令手动生成。# 生成包含所有存活对象的dump文件jmap -dump:live,formatb,file/path/to/dump.hprofPID方法C从Core文件导出如果进程已Crash但有Core文件可以使用jmap从Core文件中导出堆信息。jmap -dump:live,formatb,file/path/to/dump.hprof$JAVA_HOME/bin/javacore_file_path第三步分析Heap Dump使用专业的内存分析工具来定位问题。Eclipse MAT (Memory Analyzer Tool)功能强大免费。Histogram查看哪些类的实例数量和占用内存最多。Dominator Tree找出占用内存最多的对象及其引用链。Leak Suspects工具会自动生成内存泄漏嫌疑报告通常会直接指向问题代码。其他工具JProfiler, VisualVM等。第四步定位具体代码进阶如果通过Heap Dump只能看到是哪个对象占用内存但无法定位到具体代码行可以使用btrace进行动态追踪。原理btrace可以在不重启应用的情况下向JVM中注入探针代码追踪特定方法的调用。示例追踪com.example.MyService类中processData方法的调用。// Btrace脚本BTracepublicclassTraceMethod{OnMethod(clazzcom.example.MyService,methodprocessData)publicstaticvoidtrace(Selfcom.example.MyServiceservice,ProbeClassNameStringcls,ProbeMethodNameStringmethod){println(Method called: cls.method);jstack();// 打印调用堆栈}}3. 解决方案修复内存泄漏根据分析结果修复代码中不当的对象引用如清理static集合、正确使用ThreadLocal并调用remove()、为缓存设置过期时间等。调整JVM参数适当增大堆内存-Xms,-Xmx和元空间-XX:MetaspaceSize,-XX:MaxMetaspaceSize。优化代码避免一次性加载大量数据改用流式处理或分页查询。问题二OOM: unable to create new native thread此错误并非堆内存溢出而是操作系统层面的线程资源耗尽。1. 现象与可能原因现象应用无法创建新线程相关功能如处理新请求失败。可能原因线程数超过系统限制达到ulimit -u用户最大进程/线程数或/proc/sys/kernel/threads-max系统全局线程数上限。线程泄漏线程池配置不当如无界队列、无最大线程数限制导致线程不断创建且无法回收。内存不足每个线程都需要栈空间由-Xss参数控制如果线程过多总的虚拟内存可能耗尽。2. 排查步骤第一步统计线程数# 统计当前Java进程的线程总数ps-eLf|grepPID|wc-l第二步检查系统限制# 查看当前用户的线程数限制ulimit-u# 查看系统全局限制cat/proc/sys/kernel/threads-max# 查看当前进程的详细限制cat/proc/PID/limits|grepMax processes第三步分析线程堆栈使用jstack分析线程状态查看线程都在做什么。jstack-lPIDthread_dump.log在thread_dump.log中统计BLOCKED、WAITING、TIMED_WAITING状态的线程数量。如果发现大量线程阻塞或等待说明存在严重的锁竞争或资源等待问题。3. 解决方案调整系统限制如果线程数未达应用预期但达到系统限制可适当调大ulimit -u的值。优化线程池合理设置线程池的核心线程数、最大线程数和队列大小避免无限制创建线程。减小线程栈大小如果线程数确实很多且内存紧张可以尝试减小-Xss参数例如从默认的1M减小到256k或512k。问题三OOM: java heap space这是最典型的堆内存溢出错误。1. 现象与可能原因现象应用抛出java.lang.OutOfMemoryError: Java heap space异常并可能崩溃。可能原因与“持续Full GC”的原因高度重合主要是内存泄漏或堆空间不足。2. 排查步骤排查方法与“持续Full GC”基本一致核心是获取并分析Heap Dump。关键一步务必在JVM启动时加上-XX:HeapDumpOnOutOfMemoryError这是定位问题的“黑匣子”。分析工具使用MAT分析dump文件找到占用内存最多的对象和它们的引用链GC Roots。3. 解决方案同“持续Full GC”的解决方案。问题四Java进程意外退出进程在没有明显异常日志的情况下突然消失。1. 现象与可能原因现象进程IDPID不存在应用服务中断。可能原因被操作系统杀死OOM Killer系统物理内存不足内核为了保护系统稳定会选择一个进程杀死。代码Bug导致Crash如JNI本地代码错误、无限递归导致栈溢出等。人为操作被kill -9等命令强制终止。2. 排查步骤第一步检查系统日志这是最重要的一步查看内核日志确认进程是否被OOM Killer杀死。dmesg|grep-ikilled processdmesg|grep-iout of memory# 或者直接查看系统日志文件tail-n100/var/log/messages如果看到类似Out of memory: Kill process PID (java)的记录则说明是内存不足导致。第二步生成并分析Core Dump为了定位代码级Crash需要启用Core Dump。启用Core Dump在应用启动脚本中设置ulimit -c unlimited。查找Core文件进程Crash后Core文件通常生成在应用的工作目录下文件名可能是core或core.PID。分析Core文件Java层使用jstack分析Java线程堆栈看是否有死循环或无限递归。jstack$JAVA_HOME/bin/java core.PIDjstack_from_core.logNative层使用gdb等调试器分析定位是否是JNI代码或JVM本身的Bug。3. 解决方案内存不足增加机器内存或优化应用内存使用或调整容器的内存限制。代码Bug根据堆栈信息修复代码。问题五CPU占用过高CPU飙高会抢占系统资源影响其他服务。1. 现象与可能原因现象top命令显示Java进程的%CPU值非常高。可能原因频繁GC尤其是Full GC会消耗大量CPU。代码死循环存在未正确退出的while循环或递归。复杂计算如复杂的正则表达式回溯爆炸、序列化/反序列化、大数据量排序等。锁竞争大量线程竞争同一把锁导致上下文切换频繁内核态CPUsy升高。2. 排查步骤第一步区分CPU占用类型使用top命令观察us用户态、sy内核态、waIO等待的占比。us高通常是应用代码问题死循环、复杂计算或频繁GC。sy高通常是线程过多、频繁上下文切换或锁竞争。wa高通常是磁盘IO瓶颈不是CPU问题。第二步排除GC问题使用jstat -gcutil PID 1000观察GC情况如果FGC非常频繁则问题根源在内存。第三步定位高CPU线程如果不是GC问题则定位具体是哪个线程消耗了CPU。# 1. 以线程为单位查看CPU占用top-HpPID# 2. 找到CPU占用最高的线程ID例如是12345# 3. 将线程ID转换为16进制printf%x\n12345# 输出: 3039# 4. 在jstack日志中查找对应的线程jstackPID|grep-A20nid0x3039通过分析该线程的堆栈就能知道它在执行什么代码。例如堆栈可能指向一个正则表达式的match方法或者一个HashMap的get操作在并发场景下可能导致死循环。3. 解决方案频繁GC按内存问题处理。死循环/复杂计算优化代码逻辑修复死循环优化算法复杂度。锁竞争优化锁的粒度或使用无锁数据结构。