一、 快速诊断与初步定位
当告警响起时,首先要快速判断是 CPU 问题 还是 内存问题,或者是两者兼有。
1. 使用操作系统命令快速查看
a) 整体资源查看 - top 命令
bash
top
看 CPU:查看 %CPU 列,找到占用最高的进程ID(PID)。
看内存:查看 %MEM 列 和 RES 列,找到消耗内存最多的进程。
关键指标:load average 如果持续高于 CPU 核心数,说明系统负载过高。
b) 更精细的进程查看 - htop (如果已安装)
bash
htop
它比 top 更直观,支持颜色和鼠标操作。
c) 内存专用 - free -h
bash
free-h
查看内存和 Swap 的使用情况。如果 Swap 被频繁使用,说明物理内存不足,性能会急剧下降。
二、 深入排查:针对 Java 进程
找到占用资源高的 Java 进程 PID 后,进行深入分析。
1. CPU 过高排查
a) 找到消耗 CPU 最高的 Java 线程
使用 top -H -p <pid> 查看该 Java 进程下的所有线程。
记下 CPU 占用最高的线程 ID(例如 12345),并将其转换为十六进制(Java 线程栈中显示的是十六进制)。
bash
printf"%x\n"12345# 输出:3039
b) 获取线程转储,分析线程在做什么
使用 jstack 命令获取线程快照:
bash
jstack<pid>>jstack.log
然后在这个 jstack.log 文件中,搜索刚才转换的十六进制线程 ID 0x3039。找到对应的线程栈,就能看到这个线程正在执行什么方法、哪一行代码。通常这里就能直接定位到问题代码。
常见原因:
无限循环或密集型计算:线程栈显示在某个循环或计算逻辑中。
锁竞争激烈:大量线程处于 BLOCKED 状态,等待同一个锁。
GC 频繁:如果大量线程是 GC 线程,则问题根源在内存。
c) 使用 Arthas 在线诊断(强烈推荐)
Arthas 是阿里开源的 Java 诊断神器,无需预装,直接 attach 到线上进程进行诊断。
快速定位热点方法:
bash
# 启动 arthasjava-jararthas-boot.jar<pid># 使用 profiler 生成火焰图,直观展示 CPU 热点profiler start# ...等待几秒...profiler stop--formathtml
火焰图可以非常清晰地看到 CPU 时间都花在了哪些方法调用上。
监控方法执行时间:
bash
# 监视特定方法的调用耗时watchcom.example.YourClass yourMethod'{params, returnObj, throwExp}'-n5-x3
2. 内存过高排查
内存过高通常意味着 内存泄漏 或 对象数量/体积超出预期。
a) 查看 JVM 内存概况
使用 jstat 命令观察 GC 情况:
bash
jstat-gcutil<pid>100010# 每1秒打印一次,共10次
关注:
O (Old Space) 老年代使用率:如果一直很高且只升不降,说明很可能有内存泄漏。
FGC / FGCT:Full GC 的次数和时间。如果 FGC 很频繁且 FGCT 很长,说明 GC 在拼命工作但效果不佳,是内存问题的典型症状。
b) 生成并分析堆转储文件
这是定位内存泄漏最有效的方法。
生成堆转储:
bash
# 使用 jmap 命令 (会影响性能,谨慎在高峰使用)jmap -dump:live,format=b,file=heap_dump.hprof<pid># 或者,在 JVM 启动参数中添加,当发生 OOM 时自动生成-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/path/to/dump.hprof
分析堆转储:
使用专业工具分析 heap_dump.hprof 文件:
Eclipse MAT:功能强大,可以自动分析泄漏嫌疑。
JVisualVM:JDK 自带,基础分析足够。
JProfiler:商业软件,非常强大。
分析技巧:
在 MAT 中,查看 Histogram,按对象数量或 Shallow Heap 排序,找到占用最大的对象类型。
查看 Dominator Tree,找到内存中占据主导地位的对象(即持有大量内存引用的对象)。
查看 Leak Suspects Report(MAT 自动生成),它经常能直接给出泄漏的线索。
常见原因:
内存泄漏:静态集合类(如 Map, List)持续添加对象从未清除;未关闭的连接(数据库、网络、文件);监听器未注销。
大对象/缓存:加载了大文件到内存,或者缓存(如 Guava Cache, Ehcache)没有大小限制或过期策略。
不合理的 JVM 参数:堆内存设置过小,导致频繁 GC,或者设置过大,导致一次 Full GC 停顿时间过长。
三、 解决方案
根据排查出的根本原因,采取相应措施。
1. 针对 CPU 问题
优化算法:如果找到热点方法,优化其逻辑,减少循环次数或使用更高效的算法。
减少锁竞争:
缩小同步代码块的范围。
使用并发包下的无锁类(如 ConcurrentHashMap)。
使用读写锁(ReadWriteLock)代替同步锁。
异步化:将耗时操作(如IO、远程调用)异步化,避免阻塞工作线程。
限流和降级:对于突增的流量,在入口层进行限流,防止服务器被压垮。
2. 针对内存问题
修复内存泄漏:根据堆转储分析结果,修复代码。例如:
对于静态集合,使用弱引用(WeakHashMap)或确保有移除机制。
确保使用 try-with-resources 语句关闭所有连接。
合理使用缓存:
为缓存设置合理的最大容量和过期时间。
考虑使用分布式缓存(如 Redis)来分担单机内存压力。
优化对象创建:避免在循环中创建大量对象,重用对象(使用对象池需谨慎)。
调整 JVM 参数:
-Xmx 和 -Xms:设置堆的初始和最大大小,通常设置为相同值,避免动态调整带来的开销。
选择合适的 GC 器。对于高吞吐量应用,可用 -XX:+UseG1GC(G1 垃圾回收器);对于低延迟要求,可考虑 ZGC 或 Shenandoah。
四、 长期预防与监控
完善的监控系统:集成 APM 工具(如 SkyWalking, Pinpoint, Prometheus + Grafana),对 JVM(GC 次数、时间、内存使用率)、应用 QPS、响应时间等进行实时监控和告警。
性能测试:在上线前进行充分的压力测试,了解应用的瓶颈和最大承载能力。
代码审查:在代码层面避免常见的性能陷阱,如大对象、N+1 查询等。
定期健康检查:定期对生产系统进行 Arthas 诊断或生成堆转储,主动发现潜在问题。
总结排查流程图
text
告警触发
↓
top/free 快速定位 (CPU? 内存?)
↓
若是 CPU 高: 若是内存高:
top -H -p <pid> jstat -gcutil <pid>
printf "%x" <nid> jmap -dump ...
jstack <pid> | grep <hex-nid> MAT 分析堆转储
或使用 Arthas profiler
↓
分析线程栈/火焰图/堆转储,定位问题代码
↓
根据根本原因进行修复(优化代码/调整配置)
↓
验证、上线、持续监控
通过这套系统性的方法,绝大多数 Java 服务器的 CPU/内存问题都可以被有效地定位和解决。